Compare commits

..

29 Commits

Author SHA1 Message Date
Alex Lam S.L
34075fc4c4 v3.5.9 2019-04-27 17:00:58 +08:00
Alex Lam S.L
e5436ca566 enhance side_effects (#3384) 2019-04-25 15:15:50 +08:00
Alex Lam S.L
cfde686eab v3.5.8 2019-04-25 12:33:13 +08:00
Alex Lam S.L
a206964c0a enhance side_effects (#3383) 2019-04-25 04:14:21 +08:00
Alex Lam S.L
c56d89f804 enhance unsafe (#3382) 2019-04-25 02:42:54 +08:00
Alex Lam S.L
c215706350 enhance unsafe comparisons (#3381) 2019-04-25 00:08:08 +08:00
Alex Lam S.L
d3b93ec682 fix corner case in unsafe (#3380) 2019-04-24 22:21:28 +08:00
Alex Lam S.L
6fe20dbe33 enhance comparisons (#3379) 2019-04-24 21:38:55 +08:00
Alex Lam S.L
7ccdf3337b v3.5.7 2019-04-24 14:05:07 +08:00
Alex Lam S.L
dafed54764 fix corner case in reduce_vars (#3378)
fixes #3377
2019-04-24 14:01:01 +08:00
Alex Lam S.L
a84beafd1b fix corner case in assignments (#3376)
fixes #3375
2019-04-24 02:50:15 +08:00
Alex Lam S.L
f01cc1e413 unwind IIFE class patterns (#3373)
fixes #2332
2019-04-21 09:49:07 +08:00
Alex Lam S.L
338dd144b8 v3.5.6 2019-04-21 07:19:29 +08:00
Alex Lam S.L
c719552317 fix corner cases in functions (#3372)
fixes #3371
2019-04-21 02:16:05 +08:00
Alex Lam S.L
855964a87a enhance unsafe evaluate (#3370) 2019-04-20 19:42:41 +08:00
Alex Lam S.L
a438e2fca9 update domprops (#3369)
fixes #2343
fixes #3037
2019-04-20 07:16:14 +08:00
Alex Lam S.L
00833e893a enhance functions (#3368) 2019-04-19 19:01:47 +08:00
Alex Lam S.L
f1a77e4fc0 v3.5.5 2019-04-19 15:22:46 +08:00
Alex Lam S.L
b55a2fd531 fix corner case in functions (#3367)
fixes #3366
2019-04-19 02:55:43 +08:00
Alex Lam S.L
e8a2c0b5bf fix corner case in functions (#3365)
fixes #3364
2019-04-18 17:03:52 +08:00
Alex Lam S.L
21cd7e3f57 reduce test exports (#3361) 2019-04-17 16:19:08 +08:00
Alex Lam S.L
5172ba5f2a introduce functions (#3360)
`var f = function() {};` => `function f() {}`
2019-04-15 22:23:11 +08:00
Alex Lam S.L
a57b069409 v3.5.4 2019-04-10 02:40:42 +08:00
Alex Lam S.L
4454656c3b update dependencies (#3358)
- commander@2.20.0
- semver@6.0.0
2019-04-10 02:39:56 +08:00
Alex Lam S.L
fa43768ce0 v3.5.3 2019-04-01 18:12:03 +08:00
Alex Lam S.L
a74e600fa0 mangle shadowed lambda under ie8 correctly (#3356)
fixes #3355
2019-04-01 15:22:00 +08:00
Ruben Bridgewater
4b21526310 Fix test expectation (#3357)
The test expects a specific precision value that is not met on all V8 versions anymore due to a recent consolidation of different algorithms across the V8 code base.

This makes sure the preceision is tested against one digit less to keep the test working on all V8 versions.

Refs: 98453126c1
Refs: https://github.com/nodejs/node/issues/25060#issuecomment-477953457
2019-03-30 02:08:27 +08:00
Alex Lam S.L
a7a7b1daed v3.5.2 2019-03-23 14:25:14 +08:00
Alex Lam S.L
7436977aa5 fix infinite loop triggered by #3347 (#3354)
fixes #3353
2019-03-23 14:21:54 +08:00
25 changed files with 2963 additions and 244 deletions

View File

@@ -636,6 +636,9 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u
- `expression` (default: `false`) -- Pass `true` to preserve completion values - `expression` (default: `false`) -- Pass `true` to preserve completion values
from terminal statements without `return`, e.g. in bookmarklets. from terminal statements without `return`, e.g. in bookmarklets.
- `functions` (default: `true`) -- convert declarations from `var`to `function`
whenever possible.
- `global_defs` (default: `{}`) -- see [conditional compilation](#conditional-compilation) - `global_defs` (default: `{}`) -- see [conditional compilation](#conditional-compilation)
- `hoist_funs` (default: `false`) -- hoist function declarations - `hoist_funs` (default: `false`) -- hoist function declarations

View File

@@ -773,7 +773,7 @@ var AST_Label = DEFNODE("Label", "references", {
} }
}, AST_Symbol); }, AST_Symbol);
var AST_SymbolRef = DEFNODE("SymbolRef", null, { var AST_SymbolRef = DEFNODE("SymbolRef", "fixed", {
$documentation: "Reference to some symbol (not definition/declaration)", $documentation: "Reference to some symbol (not definition/declaration)",
}, AST_Symbol); }, AST_Symbol);

View File

@@ -60,6 +60,7 @@ function Compressor(options, false_by_default) {
drop_debugger : !false_by_default, drop_debugger : !false_by_default,
evaluate : !false_by_default, evaluate : !false_by_default,
expression : false, expression : false,
functions : !false_by_default,
global_defs : false, global_defs : false,
hoist_funs : false, hoist_funs : false,
hoist_props : !false_by_default, hoist_props : !false_by_default,
@@ -113,7 +114,7 @@ function Compressor(options, false_by_default) {
}; };
} else if (Array.isArray(pure_funcs)) { } else if (Array.isArray(pure_funcs)) {
this.pure_funcs = function(node) { this.pure_funcs = function(node) {
return pure_funcs.indexOf(node.expression.print_to_string()) < 0; return !member(node.expression.print_to_string(), pure_funcs);
}; };
} else { } else {
this.pure_funcs = return_true; this.pure_funcs = return_true;
@@ -130,7 +131,7 @@ function Compressor(options, false_by_default) {
top_retain = top_retain.split(/,/); top_retain = top_retain.split(/,/);
} }
this.top_retain = function(def) { this.top_retain = function(def) {
return top_retain.indexOf(def.name) >= 0; return member(def.name, top_retain);
}; };
} }
var toplevel = this.options["toplevel"]; var toplevel = this.options["toplevel"];
@@ -150,7 +151,7 @@ Compressor.prototype = new TreeTransformer;
merge(Compressor.prototype, { merge(Compressor.prototype, {
option: function(key) { return this.options[key] }, option: function(key) { return this.options[key] },
exposed: function(def) { exposed: function(def) {
if (def.global) for (var i = 0, len = def.orig.length; i < len; i++) if (def.global) for (var i = 0; i < def.orig.length; i++)
if (!this.toplevel[def.orig[i] instanceof AST_SymbolDefun ? "funcs" : "vars"]) if (!this.toplevel[def.orig[i] instanceof AST_SymbolDefun ? "funcs" : "vars"])
return true; return true;
return false; return false;
@@ -311,8 +312,22 @@ merge(Compressor.prototype, {
return value instanceof AST_SymbolRef && value.fixed_value() || value; return value instanceof AST_SymbolRef && value.fixed_value() || value;
} }
function is_read_only_fn(value, name) {
if (value instanceof AST_Boolean) return native_fns.Boolean[name];
if (value instanceof AST_Number) return native_fns.Number[name];
if (value instanceof AST_String) return native_fns.String[name];
if (name == "valueOf") return false;
if (value instanceof AST_Array) return native_fns.Array[name];
if (value instanceof AST_Function) return native_fns.Function[name];
if (value instanceof AST_Object) return native_fns.Object[name];
if (value instanceof AST_RegExp) return native_fns.RegExp[name];
}
function is_modified(compressor, tw, node, value, level, immutable) { function is_modified(compressor, tw, node, value, level, immutable) {
var parent = tw.parent(level); var parent = tw.parent(level);
if (compressor.option("unsafe") && parent instanceof AST_Dot && is_read_only_fn(value, parent.property)) {
return;
}
var lhs = is_lhs(node, parent); var lhs = is_lhs(node, parent);
if (lhs) return lhs; if (lhs) return lhs;
if (!immutable if (!immutable
@@ -343,7 +358,7 @@ merge(Compressor.prototype, {
def.assignments = 0; def.assignments = 0;
def.chained = false; def.chained = false;
def.direct_access = false; def.direct_access = false;
def.escaped = false; def.escaped = [];
def.fixed = !def.scope.pinned() def.fixed = !def.scope.pinned()
&& !compressor.exposed(def) && !compressor.exposed(def)
&& !(def.init instanceof AST_Function && def.init !== def.scope) && !(def.init instanceof AST_Function && def.init !== def.scope)
@@ -482,8 +497,9 @@ merge(Compressor.prototype, {
|| parent instanceof AST_Call && (node !== parent.expression || parent instanceof AST_New) || parent instanceof AST_Call && (node !== parent.expression || parent instanceof AST_New)
|| parent instanceof AST_Exit && node === parent.value && node.scope !== d.scope || parent instanceof AST_Exit && node === parent.value && node.scope !== d.scope
|| parent instanceof AST_VarDef && node === parent.value) { || parent instanceof AST_VarDef && node === parent.value) {
d.escaped.push(parent);
if (depth > 1 && !(value && value.is_constant_expression(scope))) depth = 1; if (depth > 1 && !(value && value.is_constant_expression(scope))) depth = 1;
if (!d.escaped || d.escaped > depth) d.escaped = depth; if (!d.escaped.depth || d.escaped.depth > depth) d.escaped.depth = depth;
return; return;
} else if (parent instanceof AST_Array } else if (parent instanceof AST_Array
|| parent instanceof AST_Binary && lazy_op[parent.operator] || parent instanceof AST_Binary && lazy_op[parent.operator]
@@ -535,6 +551,7 @@ merge(Compressor.prototype, {
mark_assignment_to_arguments(sym); mark_assignment_to_arguments(sym);
return; return;
} }
if (sym.fixed) delete sym.fixed;
var d = sym.definition(); var d = sym.definition();
var safe = safe_to_assign(tw, d, sym.scope, node.right); var safe = safe_to_assign(tw, d, sym.scope, node.right);
d.assignments++; d.assignments++;
@@ -544,7 +561,7 @@ merge(Compressor.prototype, {
var value = eq ? node.right : node; var value = eq ? node.right : node;
if (is_modified(compressor, tw, node, value, 0)) return; if (is_modified(compressor, tw, node, value, 0)) return;
if (!eq) d.chained = true; if (!eq) d.chained = true;
d.fixed = eq ? function() { sym.fixed = d.fixed = eq ? function() {
return node.right; return node.right;
} : function() { } : function() {
return make_node(AST_Binary, node, { return make_node(AST_Binary, node, {
@@ -558,7 +575,7 @@ merge(Compressor.prototype, {
mark(tw, d, false); mark(tw, d, false);
node.right.walk(tw); node.right.walk(tw);
mark(tw, d, true); mark(tw, d, true);
mark_escaped(tw, d, sym.scope, node, value, 0, 1); if (eq) mark_escaped(tw, d, sym.scope, node, value, 0, 1);
return true; return true;
}); });
def(AST_Binary, function(tw) { def(AST_Binary, function(tw) {
@@ -714,6 +731,7 @@ merge(Compressor.prototype, {
this.definition().fixed = false; this.definition().fixed = false;
}); });
def(AST_SymbolRef, function(tw, descend, compressor) { def(AST_SymbolRef, function(tw, descend, compressor) {
if (this.fixed) delete this.fixed;
var d = this.definition(); var d = this.definition();
d.references.push(this); d.references.push(this);
if (d.references.length == 1 if (d.references.length == 1
@@ -726,7 +744,7 @@ merge(Compressor.prototype, {
d.fixed = false; d.fixed = false;
} else if (d.fixed) { } else if (d.fixed) {
value = this.fixed_value(); value = this.fixed_value();
if (value instanceof AST_Lambda && recursive_ref(tw, d)) { if (recursive_ref(tw, d)) {
d.recursive_refs++; d.recursive_refs++;
} else if (value && ref_once(tw, compressor, d)) { } else if (value && ref_once(tw, compressor, d)) {
d.single_use = value instanceof AST_Lambda && !value.pinned() d.single_use = value instanceof AST_Lambda && !value.pinned()
@@ -741,8 +759,8 @@ merge(Compressor.prototype, {
d.fixed = false; d.fixed = false;
} }
} }
mark_escaped(tw, d, this.scope, this, value, 0, 1);
} }
mark_escaped(tw, d, this.scope, this, value, 0, 1);
var parent; var parent;
if (d.fixed instanceof AST_Defun if (d.fixed instanceof AST_Defun
&& !((parent = tw.parent()) instanceof AST_Call && parent.expression === this)) { && !((parent = tw.parent()) instanceof AST_Call && parent.expression === this)) {
@@ -781,13 +799,14 @@ merge(Compressor.prototype, {
mark_assignment_to_arguments(exp); mark_assignment_to_arguments(exp);
return; return;
} }
if (exp.fixed) delete exp.fixed;
var d = exp.definition(); var d = exp.definition();
var safe = safe_to_assign(tw, d, exp.scope, true); var safe = safe_to_assign(tw, d, exp.scope, true);
d.assignments++; d.assignments++;
var fixed = d.fixed; var fixed = d.fixed;
if (!fixed) return; if (!fixed) return;
d.chained = true; d.chained = true;
d.fixed = function() { exp.fixed = d.fixed = function() {
return make_node(AST_Binary, node, { return make_node(AST_Binary, node, {
operator: node.operator.slice(0, -1), operator: node.operator.slice(0, -1),
left: make_node(AST_UnaryPrefix, node, { left: make_node(AST_UnaryPrefix, node, {
@@ -858,10 +877,11 @@ merge(Compressor.prototype, {
this.walk(tw); this.walk(tw);
}); });
AST_Symbol.DEFMETHOD("fixed_value", function() { AST_Symbol.DEFMETHOD("fixed_value", function(final) {
var fixed = this.definition().fixed; var fixed = this.definition().fixed;
if (!fixed || fixed instanceof AST_Node) return fixed; if (!fixed) return fixed;
return fixed(); if (!final && this.fixed) fixed = this.fixed;
return fixed instanceof AST_Node ? fixed : fixed();
}); });
AST_SymbolRef.DEFMETHOD("is_immutable", function() { AST_SymbolRef.DEFMETHOD("is_immutable", function() {
@@ -1241,7 +1261,7 @@ merge(Compressor.prototype, {
// Scan case expressions first in a switch statement // Scan case expressions first in a switch statement
if (node instanceof AST_Switch) { if (node instanceof AST_Switch) {
node.expression = node.expression.transform(scanner); node.expression = node.expression.transform(scanner);
for (var i = 0, len = node.body.length; !abort && i < len; i++) { for (var i = 0; !abort && i < node.body.length; i++) {
var branch = node.body[i]; var branch = node.body[i];
if (branch instanceof AST_Case) { if (branch instanceof AST_Case) {
if (!hit) { if (!hit) {
@@ -1271,7 +1291,9 @@ merge(Compressor.prototype, {
if (node instanceof AST_Try) return true; if (node instanceof AST_Try) return true;
if (node instanceof AST_With) return true; if (node instanceof AST_With) return true;
if (replace_all) return false; if (replace_all) return false;
return node instanceof AST_SymbolRef && !node.is_declared(compressor); return node instanceof AST_SymbolRef
&& !node.is_declared(compressor)
&& !(parent instanceof AST_Assign && parent.left === node);
} }
function in_conditional(node, parent) { function in_conditional(node, parent) {
@@ -1578,9 +1600,14 @@ merge(Compressor.prototype, {
lvalues[candidate.name.name] = lhs; lvalues[candidate.name.name] = lhs;
} }
var tw = new TreeWalker(function(node) { var tw = new TreeWalker(function(node) {
var sym = root_expr(node); var value;
if (sym instanceof AST_SymbolRef || sym instanceof AST_This) { if (node instanceof AST_SymbolRef) {
lvalues[sym.name] = lvalues[sym.name] || is_modified(compressor, tw, node, node, 0); value = node.fixed_value() || node;
} else if (node instanceof AST_This) {
value = node;
}
if (value && !lvalues[node.name]) {
lvalues[node.name] = is_modified(compressor, tw, node, value, 0);
} }
}); });
expr.walk(tw); expr.walk(tw);
@@ -1690,7 +1717,7 @@ merge(Compressor.prototype, {
CHANGED = true; CHANGED = true;
statements.splice(i, 1); statements.splice(i, 1);
} else if (stat instanceof AST_Directive) { } else if (stat instanceof AST_Directive) {
if (seen_dirs.indexOf(stat.value) < 0) { if (!member(stat.value, seen_dirs)) {
i++; i++;
seen_dirs.push(stat.value); seen_dirs.push(stat.value);
} else { } else {
@@ -1887,7 +1914,7 @@ merge(Compressor.prototype, {
} }
function next_index(i) { function next_index(i) {
for (var j = i + 1, len = statements.length; j < len; j++) { for (var j = i + 1; j < statements.length; j++) {
var stat = statements[j]; var stat = statements[j];
if (!(stat instanceof AST_Var && declarations_only(stat))) { if (!(stat instanceof AST_Var && declarations_only(stat))) {
break; break;
@@ -1978,7 +2005,7 @@ merge(Compressor.prototype, {
function to_simple_statement(block, decls) { function to_simple_statement(block, decls) {
if (!(block instanceof AST_BlockStatement)) return block; if (!(block instanceof AST_BlockStatement)) return block;
var stat = null; var stat = null;
for (var i = 0, len = block.body.length; i < len; i++) { for (var i = 0; i < block.body.length; i++) {
var line = block.body[i]; var line = block.body[i];
if (line instanceof AST_Var && declarations_only(line)) { if (line instanceof AST_Var && declarations_only(line)) {
decls.push(line); decls.push(line);
@@ -2118,7 +2145,7 @@ merge(Compressor.prototype, {
function join_consecutive_vars(statements) { function join_consecutive_vars(statements) {
var defs; var defs;
for (var i = 0, j = -1, len = statements.length; i < len; i++) { for (var i = 0, j = -1; i < statements.length; i++) {
var stat = statements[i]; var stat = statements[i];
var prev = statements[j]; var prev = statements[j];
if (stat instanceof AST_Definitions) { if (stat instanceof AST_Definitions) {
@@ -2246,7 +2273,11 @@ merge(Compressor.prototype, {
}); });
def(AST_SymbolRef, function() { def(AST_SymbolRef, function() {
var fixed = this.fixed_value(); var fixed = this.fixed_value();
return fixed && fixed.is_truthy(); if (!fixed) return false;
this.is_truthy = return_false;
var result = fixed.is_truthy();
delete this.is_truthy;
return result;
}); });
})(function(node, func) { })(function(node, func) {
node.DEFMETHOD("is_truthy", func); node.DEFMETHOD("is_truthy", func);
@@ -2265,17 +2296,14 @@ merge(Compressor.prototype, {
def(AST_Node, is_strict); def(AST_Node, is_strict);
def(AST_Array, return_false); def(AST_Array, return_false);
def(AST_Assign, function(compressor) { def(AST_Assign, function(compressor) {
return this.operator == "=" return this.operator == "=" && this.right._dot_throw(compressor);
&& this.right._dot_throw(compressor); });
})
def(AST_Binary, function(compressor) { def(AST_Binary, function(compressor) {
return (this.operator == "&&" || this.operator == "||") return lazy_op[this.operator] && (this.left._dot_throw(compressor) || this.right._dot_throw(compressor));
&& (this.left._dot_throw(compressor) || this.right._dot_throw(compressor)); });
})
def(AST_Conditional, function(compressor) { def(AST_Conditional, function(compressor) {
return this.consequent._dot_throw(compressor) return this.consequent._dot_throw(compressor) || this.alternative._dot_throw(compressor);
|| this.alternative._dot_throw(compressor); });
})
def(AST_Constant, return_false); def(AST_Constant, return_false);
def(AST_Dot, function(compressor) { def(AST_Dot, function(compressor) {
if (!is_strict(compressor)) return false; if (!is_strict(compressor)) return false;
@@ -2300,7 +2328,11 @@ merge(Compressor.prototype, {
if (is_undeclared_ref(this) && this.is_declared(compressor)) return false; if (is_undeclared_ref(this) && this.is_declared(compressor)) return false;
if (this.is_immutable()) return false; if (this.is_immutable()) return false;
var fixed = this.fixed_value(); var fixed = this.fixed_value();
return !fixed || fixed._dot_throw(compressor); if (!fixed) return true;
this._dot_throw = return_true;
var result = fixed._dot_throw(compressor);
delete this._dot_throw;
return result;
}); });
def(AST_UnaryPrefix, function() { def(AST_UnaryPrefix, function() {
return this.operator == "void"; return this.operator == "void";
@@ -2311,6 +2343,51 @@ merge(Compressor.prototype, {
node.DEFMETHOD("_dot_throw", func); node.DEFMETHOD("_dot_throw", func);
}); });
(function(def) {
def(AST_Node, return_false);
def(AST_Array, return_true);
def(AST_Assign, function(compressor) {
return this.operator != "=" || this.right.is_defined(compressor);
});
def(AST_Binary, function(compressor) {
switch (this.operator) {
case "&&":
return this.left.is_defined(compressor) && this.right.is_defined(compressor);
case "||":
return this.left.is_defined(compressor) || this.right.is_defined(compressor);
default:
return true;
}
});
def(AST_Conditional, function(compressor) {
return this.consequent.is_defined(compressor) && this.alternative.is_defined(compressor);
});
def(AST_Constant, return_true);
def(AST_Lambda, return_true);
def(AST_Object, return_true);
def(AST_Sequence, function(compressor) {
return this.tail_node().is_defined(compressor);
});
def(AST_SymbolRef, function(compressor) {
if (this.is_undefined) return false;
if (is_undeclared_ref(this) && this.is_declared(compressor)) return true;
if (this.is_immutable()) return true;
var fixed = this.fixed_value();
if (!fixed) return false;
this.is_defined = return_true;
var result = fixed.is_defined(compressor);
delete this.is_defined;
return result;
});
def(AST_UnaryPrefix, function() {
return this.operator != "void";
});
def(AST_UnaryPostfix, return_true);
def(AST_Undefined, return_false);
})(function(node, func) {
node.DEFMETHOD("is_defined", func);
});
/* -----[ boolean/negation helpers ]----- */ /* -----[ boolean/negation helpers ]----- */
// methods to determine whether an expression has a boolean result type // methods to determine whether an expression has a boolean result type
@@ -2342,7 +2419,11 @@ merge(Compressor.prototype, {
}); });
def(AST_SymbolRef, function(compressor) { def(AST_SymbolRef, function(compressor) {
var fixed = this.fixed_value(); var fixed = this.fixed_value();
return fixed && fixed.is_boolean(compressor); if (!fixed) return false;
this.is_boolean = return_false;
var result = fixed.is_boolean(compressor);
delete this.is_boolean;
return result;
}); });
var unary = makePredicate("! delete"); var unary = makePredicate("! delete");
def(AST_UnaryPrefix, function() { def(AST_UnaryPrefix, function() {
@@ -2427,7 +2508,11 @@ merge(Compressor.prototype, {
}); });
def(AST_SymbolRef, function(compressor) { def(AST_SymbolRef, function(compressor) {
var fixed = this.fixed_value(); var fixed = this.fixed_value();
return fixed && fixed.is_number(compressor); if (!fixed) return false;
this.is_number = return_false;
var result = fixed.is_number(compressor);
delete this.is_number;
return result;
}); });
var unary = makePredicate("+ - ~ ++ --"); var unary = makePredicate("+ - ~ ++ --");
def(AST_Unary, function() { def(AST_Unary, function() {
@@ -2447,6 +2532,20 @@ merge(Compressor.prototype, {
return this.operator == "+" && return this.operator == "+" &&
(this.left.is_string(compressor) || this.right.is_string(compressor)); (this.left.is_string(compressor) || this.right.is_string(compressor));
}); });
var fn = makePredicate([
"charAt",
"substr",
"substring",
"toLowerCase",
"toString",
"toUpperCase",
"trim",
]);
def(AST_Call, function(compressor) {
if (!compressor.option("unsafe")) return false;
var exp = this.expression;
return exp instanceof AST_Dot && fn[exp.property];
});
def(AST_Conditional, function(compressor) { def(AST_Conditional, function(compressor) {
return this.consequent.is_string(compressor) && this.alternative.is_string(compressor); return this.consequent.is_string(compressor) && this.alternative.is_string(compressor);
}); });
@@ -2454,9 +2553,16 @@ merge(Compressor.prototype, {
return this.tail_node().is_string(compressor); return this.tail_node().is_string(compressor);
}); });
def(AST_String, return_true); def(AST_String, return_true);
def(AST_Sub, function(compressor) {
return this.expression.is_string(compressor) && this.property instanceof AST_Number;
});
def(AST_SymbolRef, function(compressor) { def(AST_SymbolRef, function(compressor) {
var fixed = this.fixed_value(); var fixed = this.fixed_value();
return fixed && fixed.is_string(compressor); if (!fixed) return false;
this.is_string = return_false;
var result = fixed.is_string(compressor);
delete this.is_string;
return result;
}); });
def(AST_UnaryPrefix, function() { def(AST_UnaryPrefix, function() {
return this.operator == "typeof"; return this.operator == "typeof";
@@ -2556,9 +2662,9 @@ merge(Compressor.prototype, {
} }
function convert_to_predicate(obj) { function convert_to_predicate(obj) {
for (var key in obj) { Object.keys(obj).forEach(function(key) {
obj[key] = makePredicate(obj[key]); obj[key] = makePredicate(obj[key]);
} });
} }
var object_fns = [ var object_fns = [
@@ -2699,7 +2805,7 @@ merge(Compressor.prototype, {
def(AST_Array, function(compressor, cached, depth) { def(AST_Array, function(compressor, cached, depth) {
if (compressor.option("unsafe")) { if (compressor.option("unsafe")) {
var elements = []; var elements = [];
for (var i = 0, len = this.elements.length; i < len; i++) { for (var i = 0; i < this.elements.length; i++) {
var element = this.elements[i]; var element = this.elements[i];
var value = element._eval(compressor, cached, depth); var value = element._eval(compressor, cached, depth);
if (element === value) return this; if (element === value) return this;
@@ -2712,7 +2818,7 @@ merge(Compressor.prototype, {
def(AST_Object, function(compressor, cached, depth) { def(AST_Object, function(compressor, cached, depth) {
if (compressor.option("unsafe")) { if (compressor.option("unsafe")) {
var val = {}; var val = {};
for (var i = 0, len = this.properties.length; i < len; i++) { for (var i = 0; i < this.properties.length; i++) {
var prop = this.properties[i]; var prop = this.properties[i];
var key = prop.key; var key = prop.key;
if (key instanceof AST_Symbol) { if (key instanceof AST_Symbol) {
@@ -2812,7 +2918,7 @@ merge(Compressor.prototype, {
var fixed = this.fixed_value(); var fixed = this.fixed_value();
if (!fixed) return this; if (!fixed) return this;
var value; var value;
if (cached.indexOf(fixed) >= 0) { if (member(fixed, cached)) {
value = fixed._eval(); value = fixed._eval();
} else { } else {
this._eval = return_this; this._eval = return_this;
@@ -2826,9 +2932,25 @@ merge(Compressor.prototype, {
} }
if (value && typeof value == "object") { if (value && typeof value == "object") {
var escaped = this.definition().escaped; var escaped = this.definition().escaped;
if (escaped && depth > escaped) return this; switch (escaped.length) {
case 0:
break;
case 1:
if (contains_ref(escaped[0], this)) break;
default:
if (depth > escaped.depth) return this;
}
} }
return value; return value;
function contains_ref(expr, ref) {
var found = false;
expr.walk(new TreeWalker(function(node) {
if (found) return true;
if (node === ref) return found = true;
}));
return found;
}
}); });
var global_objs = { var global_objs = {
Array: Array, Array: Array,
@@ -2907,12 +3029,13 @@ merge(Compressor.prototype, {
if (!native_fn || !native_fn[key]) return this; if (!native_fn || !native_fn[key]) return this;
} }
var args = []; var args = [];
for (var i = 0, len = this.args.length; i < len; i++) { for (var i = 0; i < this.args.length; i++) {
var arg = this.args[i]; var arg = this.args[i];
var value = arg._eval(compressor, cached, depth); var value = arg._eval(compressor, cached, depth);
if (arg === value) return this; if (arg === value) return this;
args.push(value); args.push(value);
} }
if (key == "replace" && typeof args[1] == "function") return this;
try { try {
return val[key].apply(val, args); return val[key].apply(val, args);
} catch (ex) { } catch (ex) {
@@ -3021,24 +3144,31 @@ merge(Compressor.prototype, {
return this.pure || !compressor.pure_funcs(this); return this.pure || !compressor.pure_funcs(this);
}); });
AST_Node.DEFMETHOD("is_call_pure", return_false); AST_Node.DEFMETHOD("is_call_pure", return_false);
AST_Dot.DEFMETHOD("is_call_pure", function(compressor) { AST_Call.DEFMETHOD("is_call_pure", function(compressor) {
if (!compressor.option("unsafe")) return; if (!compressor.option("unsafe")) return false;
var expr = this.expression; var dot = this.expression;
if (!(dot instanceof AST_Dot)) return false;
var exp = dot.expression;
var map; var map;
if (expr instanceof AST_Array) { var prop = dot.property;
if (exp instanceof AST_Array) {
map = native_fns.Array; map = native_fns.Array;
} else if (expr.is_boolean(compressor)) { } else if (exp.is_boolean(compressor)) {
map = native_fns.Boolean; map = native_fns.Boolean;
} else if (expr.is_number(compressor)) { } else if (exp.is_number(compressor)) {
map = native_fns.Number; map = native_fns.Number;
} else if (expr instanceof AST_RegExp) { } else if (exp instanceof AST_RegExp) {
map = native_fns.RegExp; map = native_fns.RegExp;
} else if (expr.is_string(compressor)) { } else if (exp.is_string(compressor)) {
map = native_fns.String; map = native_fns.String;
} else if (!this.may_throw_on_access(compressor)) { if (prop == "replace") {
var arg = this.args[1];
if (arg && !arg.is_string(compressor)) return false;
}
} else if (!dot.may_throw_on_access(compressor)) {
map = native_fns.Object; map = native_fns.Object;
} }
return map && map[this.property]; return map && map[prop];
}); });
// determine if expression has side effects // determine if expression has side effects
@@ -3063,8 +3193,7 @@ merge(Compressor.prototype, {
}); });
def(AST_Call, function(compressor) { def(AST_Call, function(compressor) {
if (!this.is_expr_pure(compressor) if (!this.is_expr_pure(compressor)
&& (!this.expression.is_call_pure(compressor) && (!this.is_call_pure(compressor) || this.expression.has_side_effects(compressor))) {
|| this.expression.has_side_effects(compressor))) {
return true; return true;
} }
return any(this.args, compressor); return any(this.args, compressor);
@@ -3443,9 +3572,7 @@ merge(Compressor.prototype, {
if (node instanceof AST_Definitions && scope === self) { if (node instanceof AST_Definitions && scope === self) {
node.definitions.forEach(function(def) { node.definitions.forEach(function(def) {
var node_def = def.name.definition(); var node_def = def.name.definition();
if (def.name instanceof AST_SymbolVar) { var_defs_by_id.add(node_def.id, def);
var_defs_by_id.add(node_def.id, def);
}
if (!drop_vars) { if (!drop_vars) {
if (!(node_def.id in in_use_ids)) { if (!(node_def.id in in_use_ids)) {
in_use_ids[node_def.id] = true; in_use_ids[node_def.id] = true;
@@ -3457,7 +3584,7 @@ merge(Compressor.prototype, {
if (def.value.has_side_effects(compressor)) { if (def.value.has_side_effects(compressor)) {
def.value.walk(tw); def.value.walk(tw);
} }
if (!node_def.chained && def.name.fixed_value() === def.value) { if (!node_def.chained && def.name.fixed_value(true) === def.value) {
fixed_ids[node_def.id] = def; fixed_ids[node_def.id] = def;
} }
} }
@@ -3552,29 +3679,49 @@ merge(Compressor.prototype, {
if (def.value && sym.id in fixed_ids && fixed_ids[sym.id] !== def) { if (def.value && sym.id in fixed_ids && fixed_ids[sym.id] !== def) {
def.value = def.value.drop_side_effect_free(compressor); def.value = def.value.drop_side_effect_free(compressor);
} }
if (def.name instanceof AST_SymbolVar) { var var_defs = var_defs_by_id.get(sym.id);
var var_defs = var_defs_by_id.get(sym.id); if (var_defs.length > 1 && (!def.value || sym.orig.indexOf(def.name) > sym.eliminated)) {
if (var_defs.length > 1 && (!def.value || sym.orig.indexOf(def.name) > sym.eliminated)) { compressor.warn("Dropping duplicated definition of variable {name} [{file}:{line},{col}]", template(def.name));
compressor.warn("Dropping duplicated definition of variable {name} [{file}:{line},{col}]", template(def.name)); if (def.value) {
if (def.value) { var ref = make_node(AST_SymbolRef, def.name, def.name);
var ref = make_node(AST_SymbolRef, def.name, def.name); sym.references.push(ref);
sym.references.push(ref); var assign = make_node(AST_Assign, def, {
var assign = make_node(AST_Assign, def, { operator: "=",
operator: "=", left: ref,
left: ref, right: def.value
right: def.value });
}); if (fixed_ids[sym.id] === def) {
if (fixed_ids[sym.id] === def) { fixed_ids[sym.id] = assign;
fixed_ids[sym.id] = assign;
}
side_effects.push(assign.transform(tt));
} }
remove(var_defs, def); side_effects.push(assign.transform(tt));
sym.eliminated++;
return;
} }
remove(var_defs, def);
sym.eliminated++;
return;
} }
if (def.value) { if (!def.value) {
head.push(def);
} else if (compressor.option("functions")
&& def.value === def.name.fixed_value()
&& def.value instanceof AST_Function
&& can_rename(def.value, def.name.name)
&& (!compressor.has_directive("use strict") || parent instanceof AST_Scope)) {
compressor.warn("Declaring {name} as function [{file}:{line},{col}]", template(def.name));
var defun = make_node(AST_Defun, def, def.value);
defun.name = make_node(AST_SymbolDefun, def.name, def.name);
var name_def = def.name.scope.resolve().def_function(defun.name);
if (def.value.name) {
var old_def = def.value.name.definition();
def.value.walk(new TreeWalker(function(node) {
if (node instanceof AST_SymbolRef && node.definition() === old_def) {
node.name = name_def.name;
node.thedef = name_def;
node.reference({});
}
}));
}
body.push(defun);
} else {
if (side_effects.length > 0) { if (side_effects.length > 0) {
if (tail.length > 0) { if (tail.length > 0) {
side_effects.push(def.value); side_effects.push(def.value);
@@ -3587,8 +3734,6 @@ merge(Compressor.prototype, {
side_effects = []; side_effects = [];
} }
tail.push(def); tail.push(def);
} else {
head.push(def);
} }
} else if (sym.orig[0] instanceof AST_SymbolCatch) { } else if (sym.orig[0] instanceof AST_SymbolCatch) {
var value = def.value && def.value.drop_side_effect_free(compressor); var value = def.value && def.value.drop_side_effect_free(compressor);
@@ -3605,6 +3750,11 @@ merge(Compressor.prototype, {
} }
sym.eliminated++; sym.eliminated++;
} }
function can_rename(fn, name) {
var def = fn.variables.get(name);
return !def || fn.name && def === fn.name.definition();
}
}); });
if (head.length > 0 || tail.length > 0) { if (head.length > 0 || tail.length > 0) {
node.definitions = head.concat(tail); node.definitions = head.concat(tail);
@@ -3630,6 +3780,7 @@ merge(Compressor.prototype, {
// https://github.com/mishoo/UglifyJS2/issues/44 // https://github.com/mishoo/UglifyJS2/issues/44
// https://github.com/mishoo/UglifyJS2/issues/1830 // https://github.com/mishoo/UglifyJS2/issues/1830
// https://github.com/mishoo/UglifyJS2/issues/1838 // https://github.com/mishoo/UglifyJS2/issues/1838
// https://github.com/mishoo/UglifyJS2/issues/3371
// that's an invalid AST. // that's an invalid AST.
// We fix it at this stage by moving the `var` outside the `for`. // We fix it at this stage by moving the `var` outside the `for`.
if (node instanceof AST_For) { if (node instanceof AST_For) {
@@ -3640,7 +3791,15 @@ merge(Compressor.prototype, {
node.init = block.body.pop(); node.init = block.body.pop();
block.body.push(node); block.body.push(node);
} }
if (node.init instanceof AST_SimpleStatement) { if (node.init instanceof AST_Defun) {
if (!block) {
block = make_node(AST_BlockStatement, node, {
body: [ node ]
});
}
block.body.splice(-1, 0, node.init);
node.init = null;
} else if (node.init instanceof AST_SimpleStatement) {
node.init = node.init.body; node.init = node.init.body;
} else if (is_empty(node.init)) { } else if (is_empty(node.init)) {
node.init = null; node.init = null;
@@ -3696,7 +3855,7 @@ merge(Compressor.prototype, {
if (node instanceof AST_Assign) { if (node instanceof AST_Assign) {
node.right.walk(tw); node.right.walk(tw);
if (node.left === sym) { if (node.left === sym) {
if (!node_def.chained && sym.fixed_value() === node.right) { if (!node_def.chained && sym.fixed_value(true) === node.right) {
fixed_ids[node_def.id] = node; fixed_ids[node_def.id] = node;
} }
if (!node.write_only) { if (!node.write_only) {
@@ -3946,7 +4105,7 @@ merge(Compressor.prototype, {
var def = sym.definition(); var def = sym.definition();
if (def.assignments != count) return; if (def.assignments != count) return;
if (def.direct_access) return; if (def.direct_access) return;
if (def.escaped == 1) return; if (def.escaped.depth == 1) return;
if (def.references.length == count) return; if (def.references.length == count) return;
if (def.single_use) return; if (def.single_use) return;
if (top_retain(def)) return; if (top_retain(def)) return;
@@ -4013,10 +4172,16 @@ merge(Compressor.prototype, {
var right = this.right.drop_side_effect_free(compressor, first_in_statement); var right = this.right.drop_side_effect_free(compressor, first_in_statement);
if (!right) return this.left.drop_side_effect_free(compressor, first_in_statement); if (!right) return this.left.drop_side_effect_free(compressor, first_in_statement);
if (lazy_op[this.operator]) { if (lazy_op[this.operator]) {
if (right === this.right) return this; var node = this;
var node = this.clone(); if (right !== node.right) {
node.right = right.drop_side_effect_free(compressor); node = this.clone();
return node; node.right = right.drop_side_effect_free(compressor);
}
return (first_in_statement ? best_of_statement : best_of_expression)(node, make_node(AST_Binary, this, {
operator: node.operator == "&&" ? "||" : "&&",
left: node.left.negate(compressor, first_in_statement),
right: node.right
}));
} else { } else {
var left = this.left.drop_side_effect_free(compressor, first_in_statement); var left = this.left.drop_side_effect_free(compressor, first_in_statement);
if (!left) return right; if (!left) return right;
@@ -4025,16 +4190,15 @@ merge(Compressor.prototype, {
}); });
def(AST_Call, function(compressor, first_in_statement) { def(AST_Call, function(compressor, first_in_statement) {
if (!this.is_expr_pure(compressor)) { if (!this.is_expr_pure(compressor)) {
if (this.expression.is_call_pure(compressor)) { var exp = this.expression;
if (this.is_call_pure(compressor)) {
var exprs = this.args.slice(); var exprs = this.args.slice();
exprs.unshift(this.expression.expression); exprs.unshift(exp.expression);
exprs = trim(exprs, compressor, first_in_statement); exprs = trim(exprs, compressor, first_in_statement);
return exprs && make_sequence(this, exprs); return exprs && make_sequence(this, exprs);
} }
if (this.expression instanceof AST_Function if (exp instanceof AST_Function && (!exp.name || !exp.name.definition().references.length)) {
&& (!this.expression.name || !this.expression.name.definition().references.length)) {
var node = this.clone(); var node = this.clone();
var exp = node.expression;
exp.process_expression(false, compressor); exp.process_expression(false, compressor);
exp.walk(new TreeWalker(function(node) { exp.walk(new TreeWalker(function(node) {
if (node instanceof AST_Return && node.value) { if (node instanceof AST_Return && node.value) {
@@ -4393,14 +4557,14 @@ merge(Compressor.prototype, {
operator : "||", operator : "||",
left : negated, left : negated,
right : self.body.body right : self.body.body
}) }).transform(compressor)
}).optimize(compressor); }).optimize(compressor);
return make_node(AST_SimpleStatement, self, { return make_node(AST_SimpleStatement, self, {
body: make_node(AST_Binary, self, { body: make_node(AST_Binary, self, {
operator : "&&", operator : "&&",
left : self.condition, left : self.condition,
right : self.body.body right : self.body.body
}) }).transform(compressor)
}).optimize(compressor); }).optimize(compressor);
} }
if (self.body instanceof AST_EmptyStatement if (self.body instanceof AST_EmptyStatement
@@ -4410,7 +4574,7 @@ merge(Compressor.prototype, {
operator : "||", operator : "||",
left : self.condition, left : self.condition,
right : self.alternative.body right : self.alternative.body
}) }).transform(compressor)
}).optimize(compressor); }).optimize(compressor);
} }
if (self.body instanceof AST_Exit if (self.body instanceof AST_Exit
@@ -4649,7 +4813,7 @@ merge(Compressor.prototype, {
&& !fn.uses_arguments && !fn.uses_arguments
&& !fn.pinned()) { && !fn.pinned()) {
var pos = 0, last = 0; var pos = 0, last = 0;
for (var i = 0, len = self.args.length; i < len; i++) { for (var i = 0; i < self.args.length; i++) {
var trim = i >= fn.argnames.length; var trim = i >= fn.argnames.length;
if (trim || fn.argnames[i].__unused) { if (trim || fn.argnames[i].__unused) {
var node = self.args[i].drop_side_effect_free(compressor); var node = self.args[i].drop_side_effect_free(compressor);
@@ -4826,15 +4990,21 @@ merge(Compressor.prototype, {
} }
break; break;
case "charAt": case "charAt":
if (exp.expression.is_string(compressor)) { if (self.args.length < 2) {
var arg = self.args[0]; var node = make_node(AST_Sub, self, {
var index = arg ? arg.evaluate(compressor) : 0; expression: exp.expression,
if (index !== arg) { property: self.args.length ? make_node(AST_Binary, self.args[0], {
return make_node(AST_Sub, exp, { operator: "|",
expression: exp.expression, left: make_node(AST_Number, self, {
property: make_node_from_constant(index | 0, arg || exp) value: 0
}).optimize(compressor); }),
} right: self.args[0]
}) : make_node(AST_Number, self, {
value: 0
})
});
node.is_string = return_true;
return node.optimize(compressor);
} }
break; break;
case "apply": case "apply":
@@ -5015,7 +5185,7 @@ merge(Compressor.prototype, {
})) { })) {
return false; return false;
} }
} else if (line instanceof AST_EmptyStatement) { } else if (line instanceof AST_Defun || line instanceof AST_EmptyStatement) {
continue; continue;
} else if (stat) { } else if (stat) {
return false; return false;
@@ -5026,33 +5196,32 @@ merge(Compressor.prototype, {
return return_value(stat); return return_value(stat);
} }
function var_exists(catches, name) {
return catches[name] || identifier_atom[name] || scope.var_names()[name];
}
function can_inject_args(catches, safe_to_inject) { function can_inject_args(catches, safe_to_inject) {
for (var i = 0, len = fn.argnames.length; i < len; i++) { for (var i = 0; i < fn.argnames.length; i++) {
var arg = fn.argnames[i]; var arg = fn.argnames[i];
if (arg.__unused) continue; if (arg.__unused) continue;
if (!safe_to_inject if (!safe_to_inject || var_exists(catches, arg.name)) return false;
|| catches[arg.name]
|| identifier_atom[arg.name]
|| scope.var_names()[arg.name]) {
return false;
}
if (in_loop) in_loop.push(arg.definition()); if (in_loop) in_loop.push(arg.definition());
} }
return true; return true;
} }
function can_inject_vars(catches, safe_to_inject) { function can_inject_vars(catches, safe_to_inject) {
for (var i = 0, len = fn.body.length; i < len; i++) { for (var i = 0; i < fn.body.length; i++) {
var stat = fn.body[i]; var stat = fn.body[i];
if (stat instanceof AST_Defun) {
if (!safe_to_inject || var_exists(catches, stat.name.name)) return false;
continue;
}
if (!(stat instanceof AST_Var)) continue; if (!(stat instanceof AST_Var)) continue;
if (!safe_to_inject) return false; if (!safe_to_inject) return false;
for (var j = stat.definitions.length; --j >= 0;) { for (var j = stat.definitions.length; --j >= 0;) {
var name = stat.definitions[j].name; var name = stat.definitions[j].name;
if (catches[name.name] if (var_exists(catches, name.name)) return false;
|| identifier_atom[name.name]
|| scope.var_names()[name.name]) {
return false;
}
if (in_loop) in_loop.push(name.definition()); if (in_loop) in_loop.push(name.definition());
} }
} }
@@ -5122,10 +5291,10 @@ merge(Compressor.prototype, {
function flatten_vars(decls, expressions) { function flatten_vars(decls, expressions) {
var pos = expressions.length; var pos = expressions.length;
for (var i = 0, lines = fn.body.length; i < lines; i++) { for (var i = 0; i < fn.body.length; i++) {
var stat = fn.body[i]; var stat = fn.body[i];
if (!(stat instanceof AST_Var)) continue; if (!(stat instanceof AST_Var)) continue;
for (var j = 0, defs = stat.definitions.length; j < defs; j++) { for (var j = 0; j < stat.definitions.length; 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(); var redef = name.definition().redefined();
@@ -5156,12 +5325,21 @@ merge(Compressor.prototype, {
flatten_args(decls, expressions); flatten_args(decls, expressions);
flatten_vars(decls, expressions); flatten_vars(decls, expressions);
expressions.push(value); expressions.push(value);
if (decls.length) { var args = fn.body.filter(function(stat) {
i = scope.body.indexOf(compressor.parent(level - 1)) + 1; if (stat instanceof AST_Defun) {
scope.body.splice(i, 0, make_node(AST_Var, fn, { var def = stat.name.definition();
definitions: decls scope.functions.set(def.name, def);
})); scope.variables.set(def.name, def);
} scope.enclosed.push(def);
scope.var_names()[def.name] = true;
return true;
}
});
args.unshift(scope.body.indexOf(compressor.parent(level - 1)) + 1, 0);
if (decls.length) args.push(make_node(AST_Var, fn, {
definitions: decls
}));
[].splice.apply(scope.body, args);
return expressions; return expressions;
} }
}); });
@@ -5350,6 +5528,7 @@ merge(Compressor.prototype, {
return this; return this;
}); });
var indexFns = makePredicate("indexOf lastIndexOf");
var commutativeOperators = makePredicate("== === != !== * & | ^"); var commutativeOperators = makePredicate("== === != !== * & | ^");
function is_object(node) { function is_object(node) {
return node instanceof AST_Array return node instanceof AST_Array
@@ -5389,6 +5568,13 @@ merge(Compressor.prototype, {
if (compressor.option("comparisons")) switch (self.operator) { if (compressor.option("comparisons")) switch (self.operator) {
case "===": case "===":
case "!==": case "!==":
if (is_undefined(self.left, compressor) && self.right.is_defined(compressor)) {
compressor.warn("Expression always defined [{file}:{line},{col}]", self.start);
return make_sequence(self, [
self.right,
make_node(self.operator == "===" ? AST_False : AST_True, self)
]).optimize(compressor);
}
var is_strict_comparison = true; var is_strict_comparison = true;
if ((self.left.is_string(compressor) && self.right.is_string(compressor)) || if ((self.left.is_string(compressor) && self.right.is_string(compressor)) ||
(self.left.is_number(compressor) && self.right.is_number(compressor)) || (self.left.is_number(compressor) && self.right.is_number(compressor)) ||
@@ -5427,6 +5613,8 @@ merge(Compressor.prototype, {
break; break;
case "&&": case "&&":
case "||": case "||":
// void 0 !== x && null !== x => null != x
// void 0 === x || null === x => null == x
var lhs = self.left; var lhs = self.left;
if (lhs.operator == self.operator) { if (lhs.operator == self.operator) {
lhs = lhs.right; lhs = lhs.right;
@@ -5455,7 +5643,8 @@ merge(Compressor.prototype, {
} }
break; break;
} }
if (compressor.option("booleans") && self.operator == "+" && compressor.in_boolean_context()) { if (compressor.option("booleans") && compressor.in_boolean_context()) switch (self.operator) {
case "+":
var ll = self.left.evaluate(compressor); var ll = self.left.evaluate(compressor);
var rr = self.right.evaluate(compressor); var rr = self.right.evaluate(compressor);
if (ll && typeof ll == "string") { if (ll && typeof ll == "string") {
@@ -5472,6 +5661,20 @@ merge(Compressor.prototype, {
make_node(AST_True, self) make_node(AST_True, self)
]).optimize(compressor); ]).optimize(compressor);
} }
break;
case "==":
if (self.left instanceof AST_String && self.left.getValue() == "" && self.right.is_string(compressor)) {
return make_node(AST_UnaryPrefix, self, {
operator: "!",
expression: self.right
}).optimize(compressor);
}
break;
case "!=":
if (self.left instanceof AST_String && self.left.getValue() == "" && self.right.is_string(compressor)) {
return self.right.optimize(compressor);
}
break;
} }
if (compressor.option("comparisons") && self.is_boolean(compressor)) { if (compressor.option("comparisons") && self.is_boolean(compressor)) {
if (!(compressor.parent() instanceof AST_Binary) if (!(compressor.parent() instanceof AST_Binary)
@@ -5491,12 +5694,12 @@ merge(Compressor.prototype, {
if (self.right instanceof AST_String if (self.right instanceof AST_String
&& self.right.getValue() == "" && self.right.getValue() == ""
&& self.left.is_string(compressor)) { && self.left.is_string(compressor)) {
return self.left; return self.left.optimize(compressor);
} }
if (self.left instanceof AST_String if (self.left instanceof AST_String
&& self.left.getValue() == "" && self.left.getValue() == ""
&& self.right.is_string(compressor)) { && self.right.is_string(compressor)) {
return self.right; return self.right.optimize(compressor);
} }
if (self.left instanceof AST_Binary if (self.left instanceof AST_Binary
&& self.left.operator == "+" && self.left.operator == "+"
@@ -5504,7 +5707,7 @@ merge(Compressor.prototype, {
&& self.left.left.getValue() == "" && self.left.left.getValue() == ""
&& self.right.is_string(compressor)) { && self.right.is_string(compressor)) {
self.left = self.left.right; self.left = self.left.right;
return self.transform(compressor); return self.optimize(compressor);
} }
} }
if (compressor.option("evaluate")) { if (compressor.option("evaluate")) {
@@ -5759,6 +5962,35 @@ merge(Compressor.prototype, {
} }
} }
} }
if (compressor.option("unsafe")
&& self.right instanceof AST_Call
&& self.right.expression instanceof AST_Dot
&& indexFns[self.right.expression.property]) {
if (compressor.option("booleans")
&& (self.operator == "==" || self.operator == "!=")
&& self.left instanceof AST_Number
&& self.left.getValue() == 0
&& compressor.in_boolean_context()) {
return (self.operator == "==" ? make_node(AST_UnaryPrefix, self, {
operator: "!",
expression: self.right
}) : self.right).optimize(compressor);
}
if (compressor.option("comparisons") && is_indexOf_match_pattern()) {
var node = make_node(AST_UnaryPrefix, self, {
operator: "!",
expression: make_node(AST_UnaryPrefix, self, {
operator: "~",
expression: self.right
})
});
if (self.operator == "!=" || self.operator == "<=") node = make_node(AST_UnaryPrefix, self, {
operator: "!",
expression: node
});
return node.optimize(compressor);
}
}
// x && (y && z) ==> x && y && z // x && (y && z) ==> x && y && z
// x || (y || z) ==> x || y || z // x || (y || z) ==> x || y || z
// x + ("y" + z) ==> x + "y" + z // x + ("y" + z) ==> x + "y" + z
@@ -5792,6 +6024,23 @@ merge(Compressor.prototype, {
if (node.is_truthy()) return true; if (node.is_truthy()) return true;
return node.evaluate(compressor); return node.evaluate(compressor);
} }
function is_indexOf_match_pattern() {
switch (self.operator) {
case ">":
case "<=":
// 0 > array.indexOf(string) => !~array.indexOf(string)
// 0 <= array.indexOf(string) => !!~array.indexOf(string)
return self.left instanceof AST_Number && self.left.getValue() == 0;
case "==":
case "!=":
// -1 == array.indexOf(string) => !~array.indexOf(string)
// -1 != array.indexOf(string) => !!~array.indexOf(string)
return self.left instanceof AST_Number && self.left.getValue() == -1
|| self.left instanceof AST_UnaryPrefix && self.left.operator == "-"
&& self.left.expression instanceof AST_Number && self.left.expression.getValue() == 1;
}
}
}); });
function recursive_ref(compressor, def) { function recursive_ref(compressor, def) {
@@ -5826,7 +6075,7 @@ merge(Compressor.prototype, {
var single_use = def.single_use && !(parent instanceof AST_Call && parent.is_expr_pure(compressor)); var single_use = def.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 (def.scope !== self.scope if (def.scope !== self.scope
&& (!compressor.option("reduce_funcs") || def.escaped == 1 || fixed.inlined)) { && (!compressor.option("reduce_funcs") || def.escaped.depth == 1 || fixed.inlined)) {
single_use = false; single_use = false;
} else if (recursive_ref(compressor, def)) { } else if (recursive_ref(compressor, def)) {
single_use = false; single_use = false;
@@ -6038,7 +6287,7 @@ merge(Compressor.prototype, {
if (parent instanceof AST_Exit) { if (parent instanceof AST_Exit) {
if (in_try(level, parent)) break; if (in_try(level, parent)) break;
if (is_reachable(def.scope, [ def ])) break; if (is_reachable(def.scope, [ def ])) break;
if (self.operator == "=") return self.right; if (self.operator == "=") return self.right.optimize(compressor);
def.fixed = false; def.fixed = false;
return make_node(AST_Binary, self, { return make_node(AST_Binary, self, {
operator: self.operator.slice(0, -1), operator: self.operator.slice(0, -1),

View File

@@ -123,7 +123,7 @@ function OutputStream(options) {
}); });
} : function(str) { } : function(str) {
var s = ""; var s = "";
for (var i = 0, len = str.length; i < len; i++) { for (var i = 0; i < str.length; i++) {
if (is_surrogate_pair_head(str[i]) && !is_surrogate_pair_tail(str[i + 1]) if (is_surrogate_pair_head(str[i]) && !is_surrogate_pair_tail(str[i + 1])
|| is_surrogate_pair_tail(str[i]) && !is_surrogate_pair_head(str[i - 1])) { || is_surrogate_pair_tail(str[i]) && !is_surrogate_pair_head(str[i - 1])) {
s += "\\u" + str.charCodeAt(i).toString(16); s += "\\u" + str.charCodeAt(i).toString(16);

View File

@@ -164,10 +164,6 @@ function is_unicode_connector_punctuation(ch) {
return UNICODE.connector_punctuation.test(ch); return UNICODE.connector_punctuation.test(ch);
} }
function is_identifier(name) {
return !RESERVED_WORDS[name] && /^[a-z_$][a-z0-9_$]*$/i.test(name);
}
function is_identifier_start(code) { function is_identifier_start(code) {
return code == 36 || code == 95 || is_letter(code); return code == 36 || code == 95 || is_letter(code);
} }
@@ -272,10 +268,8 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
function find_eol() { function find_eol() {
var text = S.text; var text = S.text;
for (var i = S.pos, n = S.text.length; i < n; ++i) { for (var i = S.pos; i < S.text.length; ++i) {
var ch = text[i]; if (NEWLINE_CHARS[text[i]]) return i;
if (NEWLINE_CHARS[ch])
return i;
} }
return -1; return -1;
} }

View File

@@ -61,8 +61,6 @@ SymbolDef.next_id = 1;
SymbolDef.prototype = { SymbolDef.prototype = {
unmangleable: function(options) { unmangleable: function(options) {
if (!options) options = {};
return this.global && !options.toplevel return this.global && !options.toplevel
|| this.undeclared || this.undeclared
|| !options.eval && this.scope.pinned() || !options.eval && this.scope.pinned()
@@ -198,24 +196,20 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
} }
if (node instanceof AST_SymbolLambda) { if (node instanceof AST_SymbolLambda) {
var def = node.thedef; var def = node.thedef;
if (def.orig.length == 1) { redefine(node, node.scope.parent_scope);
redefine(node, node.scope.parent_scope); node.thedef.init = def.init;
node.thedef.init = def.init;
}
return true; return true;
} }
})); }));
function redefine(node, scope) { function redefine(node, scope) {
var name = node.name; var name = node.name;
var refs = node.thedef.references; var old_def = node.thedef;
var def = scope.find_variable(name) || self.globals.get(name) || scope.def_variable(node); var new_def = scope.find_variable(name) || self.globals.get(name) || scope.def_variable(node);
refs.forEach(function(ref) { old_def.orig.concat(old_def.references).forEach(function(node) {
ref.thedef = def; node.thedef = new_def;
ref.reference(options); node.reference(options);
}); });
node.thedef = def;
node.reference(options);
} }
}); });
@@ -300,7 +294,7 @@ AST_Scope.DEFMETHOD("def_variable", function(symbol, init) {
AST_Lambda.DEFMETHOD("resolve", return_this); AST_Lambda.DEFMETHOD("resolve", return_this);
AST_Scope.DEFMETHOD("resolve", function() { AST_Scope.DEFMETHOD("resolve", function() {
return this.parent_scope; return this.parent_scope.resolve();
}); });
AST_Toplevel.DEFMETHOD("resolve", return_this); AST_Toplevel.DEFMETHOD("resolve", return_this);
@@ -337,7 +331,7 @@ function next_mangled_name(scope, options, def) {
} while (scope = scope.parent_scope); } while (scope = scope.parent_scope);
}); });
var name; var name;
for (var i = 0, len = holes.length; i < len; i++) { for (var i = 0; i < holes.length; i++) {
name = base54(holes[i]); name = base54(holes[i]);
if (names[name]) continue; if (names[name]) continue;
holes.splice(i, 1); holes.splice(i, 1);
@@ -346,7 +340,7 @@ function next_mangled_name(scope, options, def) {
} }
while (true) { while (true) {
name = base54(++scope.cname); name = base54(++scope.cname);
if (in_use[name] || !is_identifier(name) || options.reserved.has[name]) continue; if (in_use[name] || RESERVED_WORDS[name] || options.reserved.has[name]) continue;
if (!names[name]) break; if (!names[name]) break;
holes.push(scope.cname); holes.push(scope.cname);
} }
@@ -426,7 +420,7 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options) {
var name; var name;
do { do {
name = base54(++lname); name = base54(++lname);
} while (!is_identifier(name)); } while (RESERVED_WORDS[name]);
node.mangled_name = name; node.mangled_name = name;
return true; return true;
} }
@@ -497,7 +491,7 @@ AST_Toplevel.DEFMETHOD("expand_names", function(options) {
var name; var name;
do { do {
name = base54(cname++); name = base54(cname++);
} while (avoid[name] || !is_identifier(name)); } while (avoid[name] || RESERVED_WORDS[name]);
return name; return name;
} }
@@ -559,7 +553,7 @@ var base54 = (function() {
var freq = Object.create(null); var freq = Object.create(null);
function init(chars) { function init(chars) {
var array = []; var array = [];
for (var i = 0, len = chars.length; i < len; i++) { for (var i = 0; i < chars.length; i++) {
var ch = chars[i]; var ch = chars[i];
array.push(ch); array.push(ch);
freq[ch] = -1e-2 * i; freq[ch] = -1e-2 * i;

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.5.1", "version": "3.5.9",
"engines": { "engines": {
"node": ">=0.8.0" "node": ">=0.8.0"
}, },
@@ -23,12 +23,12 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"commander": "~2.19.0", "commander": "~2.20.0",
"source-map": "~0.6.1" "source-map": "~0.6.1"
}, },
"devDependencies": { "devDependencies": {
"acorn": "~6.1.1", "acorn": "~6.1.1",
"semver": "~5.6.0" "semver": "~6.0.0"
}, },
"scripts": { "scripts": {
"test": "node test/run-tests.js" "test": "node test/run-tests.js"

View File

@@ -289,3 +289,25 @@ increment_decrement_2: {
} }
expect_stdout: "42" expect_stdout: "42"
} }
issue_3375: {
options = {
assignments: true,
reduce_vars: true,
}
input: {
console.log(typeof function(b) {
var a = b += 1;
--b;
return a;
}("object"));
}
expect: {
console.log(typeof function(b) {
var a = b += 1;
--b;
return a;
}("object"));
}
expect_stdout: "string"
}

View File

@@ -6157,3 +6157,24 @@ sub_property: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
assign_undeclared: {
options = {
collapse_vars: true,
toplevel: true,
unused: true,
}
input: {
var A = (console.log(42), function() {});
B = new A();
console.log(typeof B);
}
expect: {
B = new (console.log(42), function() {})();
console.log(typeof B);
}
expect_stdout: [
"42",
"object",
]
}

View File

@@ -345,3 +345,38 @@ is_boolean_var: {
} }
expect_stdout: "1" expect_stdout: "1"
} }
is_defined: {
options = {
comparisons: true,
}
input: {
console.log(function a() {
return void 0 === a;
}());
}
expect: {
console.log(function a() {
return a, false;
}());
}
expect_stdout: "false"
expect_warnings: [
"WARN: Expression always defined [test/compress/comparisons.js:2,19]",
]
}
unsafe_indexOf: {
options = {
booleans: true,
comparisons: true,
unsafe: true,
}
input: {
if (Object.keys({ foo: 42 }).indexOf("foo") >= 0) console.log("PASS");
}
expect: {
if (~Object.keys({ foo: 42 }).indexOf("foo")) console.log("PASS");
}
expect_stdout: "PASS"
}

View File

@@ -1416,3 +1416,58 @@ issue_3271: {
} }
expect_stdout: "1 1" expect_stdout: "1 1"
} }
iife_condition: {
options = {
conditionals: true,
side_effects: true,
}
input: {
if (function() {
return console;
}())
console.log("PASS");
}
expect: {
!function() {
return console;
}() || console.log("PASS");
}
expect_stdout: "PASS"
}
angularjs_chain: {
options = {
conditionals: true,
passes: 2,
side_effects: true,
}
input: {
function nonComputedMember(left, right, context, create) {
var lhs = left();
if (create && create !== 1) {
if (lhs && lhs[right] == null) {
lhs[right] = {};
}
}
var value = lhs != null ? lhs[right] : undefined;
if (context) {
return { context: lhs, name: right, value: value };
} else {
return value;
}
}
}
expect: {
function nonComputedMember(left, right, context, create) {
var lhs = left();
create && 1 !== create && lhs && null == lhs[right] && (lhs[right] = {});
var value = null != lhs ? lhs[right] : void 0;
return context ? {
context: lhs,
name: right,
value: value
} : value;
}
}
}

View File

@@ -942,3 +942,21 @@ issue_2929: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
unsafe_string_replace: {
options = {
side_effects: true,
unsafe: true,
}
input: {
"foo".replace("f", function() {
console.log("PASS");
});
}
expect: {
"foo".replace("f", function() {
console.log("PASS");
});
}
expect_stdout: "PASS"
}

View File

@@ -2005,3 +2005,24 @@ issue_3233: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
issue_3375: {
options = {
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var b = 1;
var a = c = [], c = --b + ("function" == typeof f && f());
var a = c && c[a];
console.log(a, b);
}
expect: {
var b = 1;
var a = [], c = --b + ("function" == typeof f && f());
a = c && c[a];
console.log(a, b);
}
expect_stdout: "0 0"
}

View File

@@ -820,8 +820,8 @@ unsafe_charAt_noop: {
} }
expect: { expect: {
console.log( console.log(
s.charAt(0), s[0],
"string".charAt(x), "string"[0 | x],
(typeof x)[0] (typeof x)[0]
); );
} }
@@ -1124,14 +1124,14 @@ issue_2207_1: {
console.log(Math.max(3, 6, 2, 7, 3, 4)); console.log(Math.max(3, 6, 2, 7, 3, 4));
console.log(Math.cos(1.2345)); console.log(Math.cos(1.2345));
console.log(Math.cos(1.2345) - Math.sin(4.321)); console.log(Math.cos(1.2345) - Math.sin(4.321));
console.log(Math.pow(Math.PI, Math.E - Math.LN10)); console.log(Math.pow(Math.PI, Math.E - Math.LN10).toFixed(15));
} }
expect: { expect: {
console.log("A"); console.log("A");
console.log(7); console.log(7);
console.log(Math.cos(1.2345)); console.log(Math.cos(1.2345));
console.log(1.2543732512566947); console.log(1.2543732512566947);
console.log(1.6093984514472044); console.log("1.609398451447204");
} }
expect_stdout: true expect_stdout: true
} }
@@ -1687,3 +1687,46 @@ try_increment: {
} }
expect_stdout: "1" expect_stdout: "1"
} }
unsafe_escaped: {
options = {
evaluate: true,
inline: true,
passes: 3,
reduce_vars: true,
side_effects: true,
unsafe: true,
unused: true,
}
input: {
(function(a) {
console.log(function(index) {
return a[index];
}(function(term) {
return a.indexOf(term);
}("PASS")));
})([ "PASS" ]);
}
expect: {
console.log("PASS");
}
expect_stdout: "PASS"
}
unsafe_string_replace: {
options = {
evaluate: true,
unsafe: true,
}
input: {
"foo".replace("f", function() {
console.log("PASS");
});
}
expect: {
"foo".replace("f", function() {
console.log("PASS");
});
}
expect_stdout: "PASS"
}

View File

@@ -2703,3 +2703,365 @@ loop_inline: {
} }
expect_stdout: "undefined" expect_stdout: "undefined"
} }
functions: {
options = {
functions: true,
reduce_vars: true,
unused: true,
}
input: {
!function() {
var a = function a() {
return a && "a";
};
var b = function x() {
return !!x;
};
var c = function(c) {
return c;
};
if (c(b(a()))) {
var d = function() {};
var e = function y() {
return typeof y;
};
var f = function(f) {
return f;
};
console.log(a(d()), b(e()), c(f(42)), typeof d, e(), typeof f);
}
}();
}
expect: {
!function() {
function a() {
return a && "a";
}
function b() {
return !!b;
}
var c = function(c) {
return c;
};
if (c(b(a()))) {
function d() {}
function e() {
return typeof e;
}
var f = function(f) {
return f;
};
console.log(a(d()), b(e()), c(f(42)), typeof d, e(), typeof f);
}
}();
}
expect_stdout: "a true 42 function function function"
}
functions_use_strict: {
options = {
functions: true,
reduce_vars: true,
unused: true,
}
input: {
"use strict";
!function() {
var a = function a() {
return a && "a";
};
var b = function x() {
return !!x;
};
var c = function(c) {
return c;
};
if (c(b(a()))) {
var d = function() {};
var e = function y() {
return typeof y;
};
var f = function(f) {
return f;
};
console.log(a(d()), b(e()), c(f(42)), typeof d, e(), typeof f);
}
}();
}
expect: {
"use strict";
!function() {
function a() {
return a && "a";
}
function b() {
return !!b;
}
var c = function(c) {
return c;
};
if (c(b(a()))) {
var d = function() {};
var e = function y() {
return typeof y;
};
var f = function(f) {
return f;
};
console.log(a(d()), b(e()), c(f(42)), typeof d, e(), typeof f);
}
}();
}
expect_stdout: "a true 42 function function function"
}
issue_2437: {
options = {
collapse_vars: true,
conditionals: true,
functions: true,
inline: true,
join_vars: true,
passes: 2,
reduce_funcs: true,
reduce_vars: true,
sequences: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
function foo() {
return bar();
}
function bar() {
if (xhrDesc) {
var req = new XMLHttpRequest();
var result = !!req.onreadystatechange;
Object.defineProperty(XMLHttpRequest.prototype, 'onreadystatechange', xhrDesc || {});
return result;
} else {
var req = new XMLHttpRequest();
var detectFunc = function(){};
req.onreadystatechange = detectFunc;
var result = req[SYMBOL_FAKE_ONREADYSTATECHANGE_1] === detectFunc;
req.onreadystatechange = null;
return result;
}
}
console.log(foo());
}
expect: {
console.log(function() {
if (xhrDesc) {
var result = !!(req = new XMLHttpRequest()).onreadystatechange;
return Object.defineProperty(XMLHttpRequest.prototype, "onreadystatechange", xhrDesc || {}),
result;
}
function detectFunc() {}
var req;
(req = new XMLHttpRequest()).onreadystatechange = detectFunc;
result = req[SYMBOL_FAKE_ONREADYSTATECHANGE_1] === detectFunc;
return req.onreadystatechange = null, result;
}());
}
}
issue_2485: {
options = {
functions: true,
reduce_funcs: true,
reduce_vars: true,
unused: true,
}
input: {
var foo = function(bar) {
var n = function(a, b) {
return a + b;
};
var sumAll = function(arg) {
return arg.reduce(n, 0);
};
var runSumAll = function(arg) {
return sumAll(arg);
};
bar.baz = function(arg) {
var n = runSumAll(arg);
return (n.get = 1), n;
};
return bar;
};
var bar = foo({});
console.log(bar.baz([1, 2, 3]));
}
expect: {
var foo = function(bar) {
function n(a, b) {
return a + b;
}
function runSumAll(arg) {
return function(arg) {
return arg.reduce(n, 0);
}(arg);
}
bar.baz = function(arg) {
var n = runSumAll(arg);
return (n.get = 1), n;
};
return bar;
};
var bar = foo({});
console.log(bar.baz([1, 2, 3]));
}
expect_stdout: "6"
}
issue_3364: {
options = {
functions: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
mangle = {}
input: {
var s = 2, a = 100, b = 10, c = 0;
function f(p, e, r) {
try {
for (var i = 1; i-- > 0;)
var a = function(x) {
function g(y) {
y && y[a++];
}
var x = g(--s >= 0 && f(c++));
for (var j = 1; --j > 0;);
}();
} catch (e) {
try {
return;
} catch (z) {
for (var k = 1; --k > 0;) {
for (var l = 1; l > 0; --l) {
var n = function() {};
for (var k in n)
var o = (n, k);
}
}
}
}
}
var r = f();
console.log(c);
}
expect: {
var s = 2, c = 0;
(function n(r, o, a) {
try {
for (var f = 1; f-- >0;)
var t = function(r) {
(function(r) {
r && r[t++];
})(--s >= 0 && n(c++));
for (var o = 1; --o > 0;);
}();
} catch (o) {
try {
return;
} catch (r) {
for (var v = 1; --v > 0;)
for (var i = 1; i > 0;--i) {
function u() {}
for (var v in u);
}
}
}
})();
console.log(c);
}
expect_stdout: "2"
}
issue_3366: {
options = {
functions: true,
inline: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
function f() {
function g() {
return function() {};
}
var a = g();
(function() {
this && a && console.log("PASS");
})();
}
f();
}
expect: {
void function() {
this && a && console.log("PASS");
}();
function a() {}
}
expect_stdout: "PASS"
}
issue_3371: {
options = {
functions: true,
inline: true,
reduce_vars: true,
side_effects: true,
unused: true,
}
input: {
(function() {
var a = function f() {
(function() {
console.log(typeof f);
})();
};
while (a());
})();
}
expect: {
(function() {
function a() {
console.log(typeof a);
}
while (a());
})();
}
expect_stdout: "function"
}
class_iife: {
options = {
inline: true,
sequences: true,
toplevel: true,
}
input: {
var A = function() {
function B() {}
B.prototype.m = function() {
console.log("PASS");
};
return B;
}();
new A().m();
}
expect: {
var A = (B.prototype.m = function() {
console.log("PASS");
}, B);
function B() {}
new A().m();
}
expect_stdout: "PASS"
}

View File

@@ -861,3 +861,111 @@ issue_3215_4: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
issue_3355_1: {
mangle = {
ie8: false,
}
input: {
(function f() {
var f;
})();
(function g() {
})();
console.log(typeof f === typeof g);
}
expect: {
(function o() {
var o;
})();
(function o() {
})();
console.log(typeof f === typeof g);
}
expect_stdout: "true"
}
issue_3355_2: {
mangle = {
ie8: true,
}
input: {
(function f() {
var f;
})();
(function g() {
})();
console.log(typeof f === typeof g);
}
expect: {
(function f() {
var f;
})();
(function g() {
})();
console.log(typeof f === typeof g);
}
expect_stdout: "true"
}
issue_3355_3: {
mangle = {
ie8: false,
}
input: {
!function(a) {
"aaaaaaaaaa";
a();
var b = function c() {
var c = 42;
console.log("FAIL");
};
}(function() {
console.log("PASS");
});
}
expect: {
!function(a) {
"aaaaaaaaaa";
a();
var o = function a() {
var a = 42;
console.log("FAIL");
};
}(function() {
console.log("PASS");
});
}
expect_stdout: "PASS"
}
issue_3355_4: {
mangle = {
ie8: true,
}
input: {
!function(a) {
"aaaaaaaaaa";
a();
var b = function c() {
var c = 42;
console.log("FAIL");
};
}(function() {
console.log("PASS");
});
}
expect: {
!function(a) {
"aaaaaaaaaa";
a();
var o = function n() {
var n = 42;
console.log("FAIL");
};
}(function() {
console.log("PASS");
});
}
expect_stdout: "PASS"
}

View File

@@ -1,4 +1,3 @@
issue_1639_1: { issue_1639_1: {
options = { options = {
booleans: true, booleans: true,
@@ -12,7 +11,6 @@ issue_1639_1: {
} }
input: { input: {
var a = 100, b = 10; var a = 100, b = 10;
var L1 = 5; var L1 = 5;
while (--L1 > 0) { while (--L1 > 0) {
if ((--b), false) { if ((--b), false) {
@@ -21,7 +19,6 @@ issue_1639_1: {
} }
} }
} }
console.log(a, b); console.log(a, b);
} }
expect: { expect: {
@@ -29,7 +26,7 @@ issue_1639_1: {
if (--b, 0) var ignore = 0; if (--b, 0) var ignore = 0;
console.log(a, b); console.log(a, b);
} }
expect_stdout: true expect_stdout: "100 6"
} }
issue_1639_2: { issue_1639_2: {
@@ -44,25 +41,23 @@ issue_1639_2: {
} }
input: { input: {
var a = 100, b = 10; var a = 100, b = 10;
function f19() { function f19() {
if (++a, false) if (++a, false)
if (a) if (a)
if (++a); if (++a);
} }
f19(); f19();
console.log(a, b); console.log(a, b);
} }
expect: { expect: {
var a = 100, b = 10; var a = 100, b = 10;
function f19() { function f19() {
++a, 0; ++a, 1;
} }
f19(), f19(),
console.log(a, b); console.log(a, b);
} }
expect_stdout: true expect_stdout: "101 10"
} }
issue_1639_3: { issue_1639_3: {
@@ -84,5 +79,5 @@ issue_1639_3: {
a++, a++,
console.log(a, b); console.log(a, b);
} }
expect_stdout: true expect_stdout: "101 10"
} }

View File

@@ -646,3 +646,30 @@ issue_2904: {
} }
expect_stdout: "1" expect_stdout: "1"
} }
issue_3371: {
options = {
functions: true,
join_vars: true,
loops: true,
reduce_vars: true,
unused: true,
}
input: {
(function() {
var a = function() {
console.log("PASS");
};
while (a());
})();
}
expect: {
(function() {
function a() {
console.log("PASS");
}
for (; a(); );
})();
}
expect_stdout: "PASS"
}

View File

@@ -281,8 +281,8 @@ unsafe_evaluate_escaped: {
console.log(function(){ var o={p:3},a=[o]; console.log(a[0].p++); return o.p; }()); console.log(function(){ var o={p:3},a=[o]; console.log(a[0].p++); return o.p; }());
} }
expect: { expect: {
console.log(function(){ var o={p:1}; console.log(o, o.p); return o.p; }()); console.log(function(){ var o={p:1}; console.log(o, 1); return o.p; }());
console.log(function(){ var o={p:2}; console.log(o.p, o); return o.p; }()); console.log(function(){ var o={p:2}; console.log(2, o); return o.p; }());
console.log(function(){ var o={p:3},a=[o]; console.log(a[0].p++); return o.p; }()); console.log(function(){ var o={p:3},a=[o]; console.log(a[0].p++); return o.p; }());
} }
expect_stdout: true expect_stdout: true
@@ -6737,3 +6737,21 @@ drop_side_effect_free: {
} }
expect_stdout: "123" expect_stdout: "123"
} }
issue_3377: {
options = {
reduce_vars: true,
unused: true,
}
input: {
console.log(function f() {
return f[0], (f = 42);
}());
}
expect: {
console.log(function f() {
return f[0], (f = 42);
}());
}
expect_stdout: "42"
}

View File

@@ -702,7 +702,7 @@ side_effects_cascade_3: {
} }
expect: { expect: {
function f(a, b) { function f(a, b) {
!(b += a) && ((b = a) || (b -= a, b ^= a)), (b += a) || (b = a) || (b -= a, b ^= a),
a--; a--;
} }
} }
@@ -964,3 +964,39 @@ missing_link: {
console.log(a); console.log(a);
} }
} }
angularjs_chain: {
options = {
conditionals: true,
sequences: true,
side_effects: true,
}
input: {
function nonComputedMember(left, right, context, create) {
var lhs = left();
if (create && create !== 1) {
if (lhs && lhs[right] == null) {
lhs[right] = {};
}
}
var value = lhs != null ? lhs[right] : undefined;
if (context) {
return { context: lhs, name: right, value: value };
} else {
return value;
}
}
}
expect: {
function nonComputedMember(left, right, context, create) {
var lhs = left();
create && 1 !== create && lhs && null == lhs[right] && (lhs[right] = {});
var value = null != lhs ? lhs[right] : void 0;
return context ? {
context: lhs,
name: right,
value: value
} : value;
}
}
}

View File

@@ -1,7 +1,5 @@
exports["base54"] = base54;
exports["Compressor"] = Compressor; exports["Compressor"] = Compressor;
exports["defaults"] = defaults; exports["defaults"] = defaults;
exports["is_identifier"] = is_identifier;
exports["JS_Parse_Error"] = JS_Parse_Error; exports["JS_Parse_Error"] = JS_Parse_Error;
exports["mangle_properties"] = mangle_properties; exports["mangle_properties"] = mangle_properties;
exports["minify"] = minify; exports["minify"] = minify;
@@ -9,7 +7,6 @@ exports["OutputStream"] = OutputStream;
exports["parse"] = parse; exports["parse"] = parse;
exports["push_uniq"] = push_uniq; exports["push_uniq"] = push_uniq;
exports["reserve_quoted_keys"] = reserve_quoted_keys; exports["reserve_quoted_keys"] = reserve_quoted_keys;
exports["SourceMap"] = SourceMap;
exports["string_template"] = string_template; exports["string_template"] = string_template;
exports["tokenizer"] = tokenizer; exports["tokenizer"] = tokenizer;
exports["TreeTransformer"] = TreeTransformer; exports["TreeTransformer"] = TreeTransformer;

View File

@@ -3,7 +3,7 @@
"use strict"; "use strict";
var site = "https://browserbench.org/JetStream"; var site = "https://browserbench.org/JetStream1.1";
if (typeof phantom == "undefined") { if (typeof phantom == "undefined") {
require("../tools/exit"); require("../tools/exit");
var args = process.argv.slice(2); var args = process.argv.slice(2);

View File

@@ -73,8 +73,10 @@ exports.run_code = function(code, reuse) {
process.stdout.write = original_write; process.stdout.write = original_write;
if (!reuse || code.indexOf(".prototype") >= 0) { if (!reuse || code.indexOf(".prototype") >= 0) {
context = null; context = null;
} else for (var key in context) { } else {
delete context[key]; vm.runInContext(Object.keys(context).map(function(name) {
return "delete " + name;
}).join("\n"), context);
} }
} }
}; };

File diff suppressed because it is too large Load Diff

View File

@@ -1,61 +1,540 @@
<!doctype html>
<html> <html>
<head> <body>
</head> <script>
<body> !function() {
<script>(function(){ var names = [];
var props = {}; var scanned = [];
var to_scan = [];
function addObject(obj) { function scan(obj) {
if (obj == null) return; if (obj && typeof obj == "object" && !~scanned.indexOf(obj)) {
try { scanned.push(obj);
Object.getOwnPropertyNames(obj).forEach(add); to_scan.push(obj);
} catch(ex) {} }
if (obj.prototype) { }
Object.getOwnPropertyNames(obj.prototype).forEach(add);
}
if (typeof obj == "function") {
try {
Object.getOwnPropertyNames(new obj).forEach(add);
} catch(ex) {}
}
}
function add(name) { scan(self);
props[name] = true; [
} "a",
"abbr",
"acronym",
"address",
"applet",
"area",
"article",
"aside",
"audio",
"b",
"base",
"basefont",
"bdi",
"bdo",
"bgsound",
"big",
"blink",
"blockquote",
"body",
"br",
"button",
"canvas",
"caption",
"center",
"checked",
"cite",
"code",
"col",
"colgroup",
"command",
"comment",
"compact",
"content",
"data",
"datalist",
"dd",
"declare",
"defer",
"del",
"details",
"dfn",
"dialog",
"dir",
"disabled",
"div",
"dl",
"dt",
"element",
"em",
"embed",
"fieldset",
"figcaption",
"figure",
"font",
"footer",
"form",
"frame",
"frameset",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"head",
"header",
"hgroup",
"hr",
"html",
"i",
"iframe",
"image",
"img",
"input",
"ins",
"isindex",
"ismap",
"kbd",
"keygen",
"label",
"legend",
"li",
"link",
"listing",
"main",
"map",
"mark",
"marquee",
"math",
"menu",
"menuitem",
"meta",
"meter",
"multicol",
"multiple",
"nav",
"nobr",
"noembed",
"noframes",
"nohref",
"noresize",
"noscript",
"noshade",
"nowrap",
"object",
"ol",
"optgroup",
"option",
"output",
"p",
"param",
"picture",
"plaintext",
"pre",
"progress",
"q",
"rb",
"readonly",
"rp",
"rt",
"rtc",
"ruby",
"s",
"samp",
"script",
"section",
"select",
"selected",
"shadow",
"small",
"source",
"spacer",
"span",
"strike",
"strong",
"style",
"sub",
"summary",
"sup",
"svg",
"table",
"tbody",
"td",
"template",
"textarea",
"tfoot",
"th",
"thead",
"time",
"title",
"tr",
"track",
"tt",
"u",
"ul",
"var",
"video",
"wbr",
"xmp",
"XXX",
].forEach(function(tag) {
scan(document.createElement(tag));
});
[
"abort",
"absolutedeviceorientation",
"activate",
"active",
"addsourcebuffer",
"addstream",
"addtrack",
"afterprint",
"afterscriptexecute",
"afterupdate",
"animationcancel",
"animationend",
"animationiteration",
"animationstart",
"appinstalled",
"audioend",
"audioprocess",
"audiostart",
"autocomplete",
"autocompleteerror",
"auxclick",
"beforeactivate",
"beforecopy",
"beforecut",
"beforedeactivate",
"beforeeditfocus",
"beforeinstallprompt",
"beforepaste",
"beforeprint",
"beforescriptexecute",
"beforeunload",
"beforeupdate",
"blocked",
"blur",
"bounce",
"boundary",
"cached",
"cancel",
"candidatewindowhide",
"candidatewindowshow",
"candidatewindowupdate",
"canplay",
"canplaythrough",
"cellchange",
"change",
"chargingchange",
"chargingtimechange",
"checking",
"click",
"close",
"compassneedscalibration",
"complete",
"connect",
"connecting",
"connectionstatechange",
"contextmenu",
"controllerchange",
"controlselect",
"copy",
"cuechange",
"cut",
"dataavailable",
"datachannel",
"datasetchanged",
"datasetcomplete",
"dblclick",
"deactivate",
"devicechange",
"devicelight",
"devicemotion",
"deviceorientation",
"deviceorientationabsolute",
"deviceproximity",
"dischargingtimechange",
"disconnect",
"display",
"downloading",
"drag",
"dragend",
"dragenter",
"dragexit",
"dragleave",
"dragover",
"dragstart",
"drop",
"durationchange",
"emptied",
"encrypted",
"end",
"ended",
"enter",
"enterpictureinpicture",
"error",
"errorupdate",
"exit",
"filterchange",
"finish",
"focus",
"focusin",
"focusout",
"freeze",
"fullscreenchange",
"fullscreenerror",
"gesturechange",
"gestureend",
"gesturestart",
"gotpointercapture",
"hashchange",
"help",
"icecandidate",
"iceconnectionstatechange",
"icegatheringstatechange",
"inactive",
"input",
"invalid",
"keydown",
"keypress",
"keyup",
"languagechange",
"layoutcomplete",
"leavepictureinpicture",
"levelchange",
"load",
"loadeddata",
"loadedmetadata",
"loadend",
"loading",
"loadingdone",
"loadingerror",
"loadstart",
"losecapture",
"lostpointercapture",
"mark",
"message",
"messageerror",
"mousedown",
"mouseenter",
"mouseleave",
"mousemove",
"mouseout",
"mouseover",
"mouseup",
"mousewheel",
"move",
"moveend",
"movestart",
"mozfullscreenchange",
"mozfullscreenerror",
"mozorientationchange",
"mozpointerlockchange",
"mozpointerlockerror",
"mscontentzoom",
"msfullscreenchange",
"msfullscreenerror",
"msgesturechange",
"msgesturedoubletap",
"msgestureend",
"msgesturehold",
"msgesturestart",
"msgesturetap",
"msgotpointercapture",
"msinertiastart",
"mslostpointercapture",
"msmanipulationstatechanged",
"msneedkey",
"msorientationchange",
"mspointercancel",
"mspointerdown",
"mspointerenter",
"mspointerhover",
"mspointerleave",
"mspointermove",
"mspointerout",
"mspointerover",
"mspointerup",
"mssitemodejumplistitemremoved",
"msthumbnailclick",
"negotiationneeded",
"nomatch",
"noupdate",
"obsolete",
"offline",
"online",
"open",
"orientationchange",
"pagechange",
"pagehide",
"pageshow",
"paste",
"pause",
"play",
"playing",
"pluginstreamstart",
"pointercancel",
"pointerdown",
"pointerenter",
"pointerleave",
"pointerlockchange",
"pointerlockerror",
"pointermove",
"pointerout",
"pointerover",
"pointerup",
"popstate",
"progress",
"propertychange",
"ratechange",
"reading",
"readystatechange",
"rejectionhandled",
"removesourcebuffer",
"removestream",
"removetrack",
"reset",
"resize",
"resizeend",
"resizestart",
"resourcetimingbufferfull",
"result",
"resume",
"rowenter",
"rowexit",
"rowsdelete",
"rowsinserted",
"scroll",
"search",
"seeked",
"seeking",
"select",
"selectionchange",
"selectstart",
"show",
"signalingstatechange",
"soundend",
"soundstart",
"sourceclose",
"sourceclosed",
"sourceended",
"sourceopen",
"speechend",
"speechstart",
"stalled",
"start",
"statechange",
"stop",
"storage",
"storagecommit",
"submit",
"success",
"suspend",
"textinput",
"timeout",
"timeupdate",
"toggle",
"touchcancel",
"touchend",
"touchmove",
"touchstart",
"track",
"transitioncancel",
"transitionend",
"transitionrun",
"transitionstart",
"unhandledrejection",
"unload",
"updateready",
"upgradeneeded",
"userproximity",
"versionchange",
"visibilitychange",
"voiceschanged",
"volumechange",
"vrdisplayactivate",
"vrdisplayconnect",
"vrdisplaydeactivate",
"vrdisplaydisconnect",
"vrdisplaypresentchange",
"waiting",
"waitingforkey",
"warning",
"webkitanimationend",
"webkitanimationiteration",
"webkitanimationstart",
"webkitcurrentplaybacktargetiswirelesschanged",
"webkitfullscreenchange",
"webkitfullscreenerror",
"webkitkeyadded",
"webkitkeyerror",
"webkitkeymessage",
"webkitneedkey",
"webkitorientationchange",
"webkitplaybacktargetavailabilitychanged",
"webkitpointerlockchange",
"webkitpointerlockerror",
"webkitresourcetimingbufferfull",
"webkittransitionend",
"wheel",
"zoom",
].forEach(function(type) {
[
"beforeunloadevent",
"compositionevent",
"customevent",
"devicemotionevent",
"deviceorientationevent",
"dragevent",
"event",
"events",
"focusevent",
"hashchangeevent",
"htmlevents",
"keyboardevent",
"messageevent",
"mouseevent",
"mouseevents",
"storageevent",
"svgevents",
"textevent",
"touchevent",
"uievent",
"uievents",
].forEach(function(interface) {
try {
var event = document.createEvent(interface);
event.initEvent(type, true, true);
scan(event);
} catch (e) {}
});
});
Object.getOwnPropertyNames(window).forEach(function(thing){ var obj;
addObject(window[thing]); while (obj = to_scan.shift()) {
}); var proto = obj;
do {
try { Object.getOwnPropertyNames(proto).forEach(function(name) {
addObject(new Event("click")); var visited = ~names.indexOf(name);
addObject(new Event("contextmenu")); if (!visited) names.push(name);
addObject(new Event("mouseup")); try {
addObject(new Event("mousedown")); scan(obj[name]);
addObject(new Event("keydown")); if (visited) return;
addObject(new Event("keypress")); if (/^create/.test(name)) {
addObject(new Event("keyup")); scan(obj[name]());
} catch(ex) {} }
if (/^[A-Z]/.test(name)) {
var ta = document.createElement("textarea"); scan(new obj[name]());
ta.style.width = "100%"; }
ta.style.height = "20em"; } catch (e) {}
ta.style.boxSizing = "border-box"; });
<!-- ta.value = Object.keys(props).sort(cmp).map(function(name){ --> } while (proto = Object.getPrototypeOf(proto));
<!-- return JSON.stringify(name); --> }
<!-- }).join(",\n"); --> names.sort();
ta.value = JSON.stringify({ document.write('<pre>[\n "');
vars: [], document.write(names.join('",\n "'));
props: Object.keys(props).sort(cmp) document.write('"\n]</pre>');
}, null, 2); }();
document.body.appendChild(ta); </script>
</body>
function cmp(a, b) {
a = a.toLowerCase();
b = b.toLowerCase();
return a < b ? -1 : a > b ? 1 : 0;
}
})();</script>
</body>
</html> </html>