Compare commits

...

10 Commits

Author SHA1 Message Date
Alex Lam S.L
59eecb6bf5 v3.3.22 2018-04-20 19:50:16 +00:00
Alex Lam S.L
d83c6490ab fix corner case in strip_func_ids() (#3090) 2018-04-19 04:51:42 +08:00
Alex Lam S.L
7362f57966 improve performance when handling unused variables in collapse_vars (#3084)
fixes #3082
2018-04-15 12:38:31 +08:00
Alex Lam S.L
eaa2c1f6af v3.3.21 2018-04-12 07:08:53 +00:00
Alex Lam S.L
6a916523d4 fix inline of catch-scoped variables (#3077)
fixes #3076
2018-04-11 15:44:43 +08:00
Alex Lam S.L
ba7069d52b suppress hoist_props for embedded assignments (#3074) 2018-04-11 05:19:16 +08:00
Alex Lam S.L
4dd7d0e39b extend hoist_props (#3073)
- handle `AST_Assign` the same way as `AST_VarDef`
- inject `AST_Var` as succeeding statement

fixes #3071
2018-04-11 02:48:15 +08:00
Alex Lam S.L
90199d0a96 extend join_vars on object assignments (#3072) 2018-04-11 01:35:42 +08:00
Alex Lam S.L
b82fd0ad41 handle flow control in loops with reduce_vars (#3069)
fixes #3068
2018-04-10 06:51:03 +08:00
Alex Lam S.L
183da16896 handle pure_funcs under inline & reduce_vars correctly (#3066)
fixes #3065
2018-04-10 02:46:38 +08:00
9 changed files with 486 additions and 65 deletions

View File

@@ -685,7 +685,8 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u
pass `pure_funcs: [ 'Math.floor' ]` to let it know that this pass `pure_funcs: [ 'Math.floor' ]` to let it know that this
function won't produce any side effect, in which case the whole function won't produce any side effect, in which case the whole
statement would get discarded. The current implementation adds some statement would get discarded. The current implementation adds some
overhead (compression will be slower). overhead (compression will be slower). Make sure symbols under `pure_funcs`
are also under `mangle.reserved` to avoid mangling.
- `pure_getters` (default: `"strict"`) -- If you pass `true` for - `pure_getters` (default: `"strict"`) -- If you pass `true` for
this, UglifyJS will assume that object property access this, UglifyJS will assume that object property access

View File

@@ -530,11 +530,16 @@ merge(Compressor.prototype, {
tw.safe_ids = save_ids; tw.safe_ids = save_ids;
return true; return true;
}); });
def(AST_DWLoop, function(tw, descend) { def(AST_Do, function(tw) {
var saved_loop = tw.in_loop; var saved_loop = tw.in_loop;
tw.in_loop = this; tw.in_loop = this;
push(tw); push(tw);
descend(); this.body.walk(tw);
if (has_break_or_continue(this)) {
pop(tw);
push(tw);
}
this.condition.walk(tw);
pop(tw); pop(tw);
tw.in_loop = saved_loop; tw.in_loop = saved_loop;
return true; return true;
@@ -543,19 +548,17 @@ merge(Compressor.prototype, {
if (this.init) this.init.walk(tw); if (this.init) this.init.walk(tw);
var saved_loop = tw.in_loop; var saved_loop = tw.in_loop;
tw.in_loop = this; tw.in_loop = this;
if (this.condition) {
push(tw);
this.condition.walk(tw);
pop(tw);
}
push(tw); push(tw);
if (this.condition) this.condition.walk(tw);
this.body.walk(tw); this.body.walk(tw);
pop(tw);
if (this.step) { if (this.step) {
push(tw); if (has_break_or_continue(this)) {
pop(tw);
push(tw);
}
this.step.walk(tw); this.step.walk(tw);
pop(tw);
} }
pop(tw);
tw.in_loop = saved_loop; tw.in_loop = saved_loop;
return true; return true;
}); });
@@ -713,6 +716,15 @@ merge(Compressor.prototype, {
} }
} }
}); });
def(AST_While, function(tw, descend) {
var saved_loop = tw.in_loop;
tw.in_loop = this;
push(tw);
descend();
pop(tw);
tw.in_loop = saved_loop;
return true;
});
})(function(node, func){ })(function(node, func){
node.DEFMETHOD("reduce_vars", func); node.DEFMETHOD("reduce_vars", func);
}); });
@@ -1336,8 +1348,9 @@ merge(Compressor.prototype, {
if (expr instanceof AST_VarDef) { if (expr instanceof AST_VarDef) {
var def = expr.name.definition(); var def = expr.name.definition();
if (!member(expr.name, def.orig)) return; if (!member(expr.name, def.orig)) return;
var declared = def.orig.length - def.eliminated;
var referenced = def.references.length - def.replaced; var referenced = def.references.length - def.replaced;
if (!referenced) return;
var declared = def.orig.length - def.eliminated;
if (declared > 1 && !(expr.name instanceof AST_SymbolFunarg) if (declared > 1 && !(expr.name instanceof AST_SymbolFunarg)
|| (referenced > 1 ? mangleable_var(expr) : !compressor.exposed(def))) { || (referenced > 1 ? mangleable_var(expr) : !compressor.exposed(def))) {
return make_node(AST_SymbolRef, expr.name, expr.name); return make_node(AST_SymbolRef, expr.name, expr.name);
@@ -1870,9 +1883,6 @@ merge(Compressor.prototype, {
} }
function join_object_assignments(defn, body) { function join_object_assignments(defn, body) {
if (!(defn instanceof AST_Definitions)) return;
var def = defn.definitions[defn.definitions.length - 1];
if (!(def.value instanceof AST_Object)) return;
var exprs; var exprs;
if (body instanceof AST_Assign) { if (body instanceof AST_Assign) {
exprs = [ body ]; exprs = [ body ];
@@ -1880,6 +1890,23 @@ merge(Compressor.prototype, {
exprs = body.expressions.slice(); exprs = body.expressions.slice();
} }
if (!exprs) return; if (!exprs) return;
if (defn instanceof AST_Definitions) {
var def = defn.definitions[defn.definitions.length - 1];
if (trim_object_assignments(def.name, def.value, exprs)) return exprs;
}
for (var i = exprs.length - 1; --i >= 0;) {
var expr = exprs[i];
if (!(expr instanceof AST_Assign)) continue;
if (expr.operator != "=") continue;
if (!(expr.left instanceof AST_SymbolRef)) continue;
var tail = exprs.slice(i + 1);
if (!trim_object_assignments(expr.left, expr.right, tail)) continue;
return exprs.slice(0, i + 1).concat(tail);
}
}
function trim_object_assignments(name, value, exprs) {
if (!(value instanceof AST_Object)) return;
var trimmed = false; var trimmed = false;
do { do {
var node = exprs[0]; var node = exprs[0];
@@ -1888,7 +1915,7 @@ merge(Compressor.prototype, {
if (!(node.left instanceof AST_PropAccess)) break; if (!(node.left instanceof AST_PropAccess)) break;
var sym = node.left.expression; var sym = node.left.expression;
if (!(sym instanceof AST_SymbolRef)) break; if (!(sym instanceof AST_SymbolRef)) break;
if (def.name.name != sym.name) break; if (name.name != sym.name) break;
if (!node.right.is_constant_expression(scope)) break; if (!node.right.is_constant_expression(scope)) break;
var prop = node.left.property; var prop = node.left.property;
if (prop instanceof AST_Node) { if (prop instanceof AST_Node) {
@@ -1901,15 +1928,15 @@ merge(Compressor.prototype, {
} : function(node) { } : function(node) {
return node.key.name != prop; return node.key.name != prop;
}; };
if (!all(def.value.properties, diff)) break; if (!all(value.properties, diff)) break;
def.value.properties.push(make_node(AST_ObjectKeyVal, node, { value.properties.push(make_node(AST_ObjectKeyVal, node, {
key: prop, key: prop,
value: node.right value: node.right
})); }));
exprs.shift(); exprs.shift();
trimmed = true; trimmed = true;
} while (exprs.length); } while (exprs.length);
return trimmed && exprs; return trimmed;
} }
function join_consecutive_vars(statements) { function join_consecutive_vars(statements) {
@@ -3613,28 +3640,50 @@ merge(Compressor.prototype, {
var top_retain = self instanceof AST_Toplevel && compressor.top_retain || return_false; var top_retain = self instanceof AST_Toplevel && compressor.top_retain || return_false;
var defs_by_id = Object.create(null); var defs_by_id = Object.create(null);
return self.transform(new TreeTransformer(function(node, descend) { return self.transform(new TreeTransformer(function(node, descend) {
if (node instanceof AST_VarDef) { if (node instanceof AST_Assign
var sym = node.name, def, value; && node.operator == "="
if (sym.scope === self && node.write_only
&& (def = sym.definition()).escaped != 1 && can_hoist(node.left, node.right, 1)) {
&& !def.assignments descend(node, this);
&& !def.direct_access var defs = new Dictionary();
&& !def.single_use var assignments = [];
&& !top_retain(def) var decls = [];
&& (value = sym.fixed_value()) === node.value node.right.properties.forEach(function(prop) {
&& value instanceof AST_Object) { var decl = make_sym(node.left, prop.key);
descend(node, this); decls.push(make_node(AST_VarDef, node, {
var defs = new Dictionary(); name: decl,
var assignments = []; value: null
value.properties.forEach(function(prop) { }));
assignments.push(make_node(AST_VarDef, node, { var sym = make_node(AST_SymbolRef, node, {
name: make_sym(prop.key), name: decl.name,
value: prop.value scope: self,
})); thedef: decl.definition()
}); });
defs_by_id[def.id] = defs; sym.reference({});
return MAP.splice(assignments); assignments.push(make_node(AST_Assign, node, {
} operator: "=",
left: sym,
right: prop.value
}));
});
defs_by_id[node.left.definition().id] = defs;
self.body.splice(self.body.indexOf(this.stack[1]) + 1, 0, make_node(AST_Var, node, {
definitions: decls
}));
return make_sequence(node, assignments);
}
if (node instanceof AST_VarDef && can_hoist(node.name, node.value, 0)) {
descend(node, this);
var defs = new Dictionary();
var var_defs = [];
node.value.properties.forEach(function(prop) {
var_defs.push(make_node(AST_VarDef, node, {
name: make_sym(node.name, prop.key),
value: prop.value
}));
});
defs_by_id[node.name.definition().id] = defs;
return MAP.splice(var_defs);
} }
if (node instanceof AST_PropAccess && node.expression instanceof AST_SymbolRef) { if (node instanceof AST_PropAccess && node.expression instanceof AST_SymbolRef) {
var defs = defs_by_id[node.expression.definition().id]; var defs = defs_by_id[node.expression.definition().id];
@@ -3650,8 +3699,20 @@ merge(Compressor.prototype, {
} }
} }
function make_sym(key) { function can_hoist(sym, right, count) {
var new_var = make_node(sym.CTOR, sym, { if (sym.scope !== self) return;
var def = sym.definition();
if (def.assignments != count) return;
if (def.direct_access) return;
if (def.escaped == 1) return;
if (def.single_use) return;
if (top_retain(def)) return;
if (sym.fixed_value() !== right) return;
return right instanceof AST_Object;
}
function make_sym(sym, key) {
var new_var = make_node(AST_SymbolVar, sym, {
name: self.make_var_name(sym.name + "_" + key), name: self.make_var_name(sym.name + "_" + key),
scope: self scope: self
}); });
@@ -3837,6 +3898,20 @@ merge(Compressor.prototype, {
return compressor.option("loops") ? make_node(AST_For, self, self).optimize(compressor) : self; return compressor.option("loops") ? make_node(AST_For, self, self).optimize(compressor) : self;
}); });
function has_break_or_continue(loop, parent) {
var found = false;
var tw = new TreeWalker(function(node) {
if (found || node instanceof AST_Scope) return true;
if (node instanceof AST_LoopControl && tw.loopcontrol_target(node) === loop) {
return found = true;
}
});
if (parent instanceof AST_LabeledStatement) tw.push(parent);
tw.push(loop);
loop.body.walk(tw);
return found;
}
OPT(AST_Do, function(self, compressor){ OPT(AST_Do, function(self, compressor){
if (!compressor.option("loops")) return self; if (!compressor.option("loops")) return self;
var cond = self.condition.is_truthy() || self.condition.tail_node().evaluate(compressor); var cond = self.condition.is_truthy() || self.condition.tail_node().evaluate(compressor);
@@ -3851,22 +3926,16 @@ merge(Compressor.prototype, {
] ]
}) })
}).optimize(compressor); }).optimize(compressor);
var has_loop_control = false; if (!has_break_or_continue(self, compressor.parent())) {
var tw = new TreeWalker(function(node) { return make_node(AST_BlockStatement, self.body, {
if (node instanceof AST_Scope || has_loop_control) return true; body: [
if (node instanceof AST_LoopControl && tw.loopcontrol_target(node) === self) self.body,
return has_loop_control = true; make_node(AST_SimpleStatement, self.condition, {
}); body: self.condition
var parent = compressor.parent(); })
(parent instanceof AST_LabeledStatement ? parent : self).walk(tw); ]
if (!has_loop_control) return make_node(AST_BlockStatement, self.body, { }).optimize(compressor);
body: [ }
self.body,
make_node(AST_SimpleStatement, self.condition, {
body: self.condition
})
]
}).optimize(compressor);
} }
if (self.body instanceof AST_SimpleStatement) return make_node(AST_For, self, { if (self.body instanceof AST_SimpleStatement) return make_node(AST_For, self, {
condition: make_sequence(self.condition, [ condition: make_sequence(self.condition, [
@@ -4573,7 +4642,8 @@ merge(Compressor.prototype, {
} }
} }
var stat = is_func && fn.body[0]; var stat = is_func && fn.body[0];
if (compressor.option("inline") && stat instanceof AST_Return) { var can_inline = compressor.option("inline") && !self.is_expr_pure(compressor);
if (can_inline && stat instanceof AST_Return) {
var value = stat.value; var value = stat.value;
if (!value || value.is_constant_expression()) { if (!value || value.is_constant_expression()) {
if (value) { if (value) {
@@ -4587,7 +4657,7 @@ merge(Compressor.prototype, {
} }
if (is_func) { if (is_func) {
var def, value, scope, in_loop, level = -1; var def, value, scope, in_loop, level = -1;
if (compressor.option("inline") if (can_inline
&& !fn.uses_arguments && !fn.uses_arguments
&& !fn.uses_eval && !fn.uses_eval
&& !(fn.name && fn instanceof AST_Function) && !(fn.name && fn instanceof AST_Function)
@@ -4772,6 +4842,11 @@ merge(Compressor.prototype, {
for (var j = 0, defs = stat.definitions.length; j < defs; j++) { for (var j = 0, defs = stat.definitions.length; j < defs; j++) {
var var_def = stat.definitions[j]; var var_def = stat.definitions[j];
var name = var_def.name; var name = var_def.name;
var redef = name.definition().redefined();
if (redef) {
name = name.clone();
name.thedef = redef;
}
append_var(decls, expressions, name, var_def.value); append_var(decls, expressions, name, var_def.value);
if (in_loop && all(fn.argnames, function(argname) { if (in_loop && all(fn.argnames, function(argname) {
return argname.name != name.name; return argname.name != name.name;
@@ -5460,11 +5535,12 @@ merge(Compressor.prototype, {
return make_node(AST_Infinity, self).optimize(compressor); return make_node(AST_Infinity, self).optimize(compressor);
} }
} }
if (compressor.option("reduce_vars") var parent = compressor.parent();
&& is_lhs(self, compressor.parent()) !== self) { if (compressor.option("reduce_vars") && is_lhs(self, parent) !== self) {
var d = self.definition(); var d = self.definition();
var fixed = self.fixed_value(); var fixed = self.fixed_value();
var single_use = d.single_use; var single_use = d.single_use
&& !(parent instanceof AST_Call && parent.is_expr_pure(compressor));
if (single_use && fixed instanceof AST_Lambda) { if (single_use && fixed instanceof AST_Lambda) {
if (d.scope !== self.scope if (d.scope !== self.scope
&& (!compressor.option("reduce_funcs") && (!compressor.option("reduce_funcs")

View File

@@ -3,7 +3,7 @@
"description": "JavaScript parser, mangler/compressor and beautifier toolkit", "description": "JavaScript parser, mangler/compressor and beautifier toolkit",
"author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)", "author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"version": "3.3.20", "version": "3.3.22",
"engines": { "engines": {
"node": ">=0.8.0" "node": ">=0.8.0"
}, },

View File

@@ -2267,3 +2267,39 @@ issue_3054: {
} }
expect_stdout: "true true" expect_stdout: "true true"
} }
issue_3076: {
options = {
dead_code: true,
inline: true,
sequences: true,
unused: true,
}
input: {
var c = "PASS";
(function(b) {
var n = 2;
while (--b + function() {
e && (c = "FAIL");
e = 5;
return 1;
try {
var a = 5;
} catch (e) {
var e;
}
}().toString() && --n > 0);
})(2);
console.log(c);
}
expect: {
var c = "PASS";
(function(b) {
var n = 2;
while (--b + (e = void 0, e && (c = "FAIL"), e = 5, 1).toString() && --n > 0);
var e;
})(2),
console.log(c);
}
expect_stdout: "PASS"
}

View File

@@ -742,3 +742,117 @@ issue_3046: {
} }
expect_stdout: "1" expect_stdout: "1"
} }
issue_3071_1: {
options = {
evaluate: true,
inline: true,
join_vars: true,
hoist_props: true,
passes: 3,
reduce_vars: true,
sequences: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
(function() {
var obj = {};
obj.one = 1;
obj.two = 2;
console.log(obj.one);
})();
}
expect: {
console.log(1);
}
expect_stdout: "1"
}
issue_3071_2: {
options = {
evaluate: true,
inline: true,
join_vars: true,
hoist_props: true,
passes: 3,
reduce_vars: true,
sequences: true,
side_effects: true,
unused: true,
}
input: {
(function() {
obj = {};
obj.one = 1;
obj.two = 2;
console.log(obj.one);
var obj;
})();
}
expect: {
console.log(1);
}
expect_stdout: "1"
}
issue_3071_2_toplevel: {
options = {
evaluate: true,
inline: true,
join_vars: true,
hoist_props: true,
passes: 3,
reduce_vars: true,
sequences: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
(function() {
obj = {};
obj.one = 1;
obj.two = 2;
console.log(obj.one);
var obj;
})();
}
expect: {
console.log(1);
}
expect_stdout: "1"
}
issue_3071_3: {
options = {
hoist_props: true,
reduce_vars: true,
}
input: {
var c = 0;
(function(a, b) {
(function f(o) {
var n = 2;
while (--b + (o = {
p: c++,
}) && --n > 0);
})();
})();
console.log(c);
}
expect: {
var c = 0;
(function(a, b) {
(function f(o) {
var n = 2;
while (--b + (o = {
p: c++,
}) && --n > 0);
})();
})();
console.log(c);
}
expect_stdout: "2"
}

View File

@@ -1208,6 +1208,37 @@ join_object_assignments_3: {
expect_stdout: "PASS" expect_stdout: "PASS"
} }
join_object_assignments_4: {
options = {
join_vars: true,
sequences: true,
}
input: {
var o;
console.log(o);
o = {};
o.a = "foo";
console.log(o.b);
o.b = "bar";
console.log(o.a);
}
expect: {
var o;
console.log(o),
o = {
a: "foo",
},
console.log(o.b),
o.b = "bar",
console.log(o.a);
}
expect_stdout: [
"undefined",
"undefined",
"foo",
]
}
join_object_assignments_return_1: { join_object_assignments_return_1: {
options = { options = {
join_vars: true, join_vars: true,

View File

@@ -535,3 +535,110 @@ issue_2705_6: {
"/* */new(/* */a()||b())(c(),d());", "/* */new(/* */a()||b())(c(),d());",
] ]
} }
issue_3065_1: {
options = {
inline: true,
pure_funcs: [ "pureFunc" ],
reduce_vars: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
function modifyWrapper(a, f, wrapper) {
wrapper.a = a;
wrapper.f = f;
return wrapper;
}
function pureFunc(fun) {
return modifyWrapper(1, fun, function(a) {
return fun(a);
});
}
var unused = pureFunc(function(x) {
return x;
});
}
expect: {}
}
issue_3065_2: {
rename = true
options = {
inline: true,
pure_funcs: [ "pureFunc" ],
reduce_vars: true,
side_effects: true,
toplevel: true,
unused: true,
}
mangle = {
reserved: [ "pureFunc" ],
toplevel: true,
}
input: {
function modifyWrapper(a, f, wrapper) {
wrapper.a = a;
wrapper.f = f;
return wrapper;
}
function pureFunc(fun) {
return modifyWrapper(1, fun, function(a) {
return fun(a);
});
}
var unused = pureFunc(function(x) {
return x;
});
}
expect: {}
}
issue_3065_3: {
options = {
pure_funcs: [ "debug" ],
reduce_vars: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
function debug(msg) {
console.log(msg);
}
debug(function() {
console.log("PASS");
return "FAIL";
}());
}
expect: {
(function() {
console.log("PASS");
})();
}
}
issue_3065_4: {
options = {
pure_funcs: [ "debug" ],
reduce_vars: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
var debug = function(msg) {
console.log(msg);
};
debug(function() {
console.log("PASS");
return "FAIL";
}());
}
expect: {
(function() {
console.log("PASS");
})();
}
}

View File

@@ -5654,3 +5654,59 @@ issue_3042_2: {
"true", "true",
] ]
} }
issue_3068_1: {
options = {
evaluate: true,
reduce_vars: true,
}
input: {
(function() {
do {
continue;
var b = "defined";
} while (b && b.c);
})();
}
expect: {
(function() {
do {
continue;
var b = "defined";
} while (b && b.c);
})();
}
expect_stdout: true
}
issue_3068_2: {
options = {
evaluate: true,
reduce_vars: true,
}
input: {
(function() {
do {
try {
while ("" == typeof a);
} finally {
continue;
}
var b = "defined";
} while (b && b.c);
})();
}
expect: {
(function() {
do {
try {
while ("" == typeof a);
} finally {
continue;
}
var b = "defined";
} while (b && b.c);
})();
}
expect_stdout: true
}

View File

@@ -40,7 +40,7 @@ function safe_log(arg, level) {
} }
function strip_func_ids(text) { function strip_func_ids(text) {
return text.toString().replace(/F[0-9]{6}N/g, "<F<>N>"); return ("" + text).replace(/F[0-9]{6}N/g, "<F<>N>");
} }
var context; var context;