Compare commits

...

32 Commits

Author SHA1 Message Date
Alex Lam S.L
5beb7e4797 v3.7.5 2020-01-12 11:12:11 +08:00
Alex Lam S.L
46caaa82ba enhance collapse_vars (#3680)
closes #3679
2020-01-10 04:28:43 +08:00
Alex Lam S.L
5d258259a4 introduce --output-opts CLI option (#3678)
closes #3675
2020-01-08 20:44:03 +08:00
Alex Lam S.L
14c35739dd fix corner case in unsafe_math (#3677)
fixes #3676
2020-01-08 10:28:10 +08:00
Alex Lam S.L
f5ceff6e4b fix corner case in unused (#3674)
fixes #3673
2020-01-07 20:06:25 +08:00
Alex Lam S.L
4d6771b9b1 fix corner case in collapse_vars (#3672)
fixes #3671
2020-01-07 19:34:16 +08:00
Alex Lam S.L
d17191111a v3.7.4 2020-01-07 07:59:54 +08:00
Alex Lam S.L
0ff607cb80 improve ufuzz false positive detection (#3670) 2020-01-06 11:26:15 +08:00
Alex Lam S.L
1988495d71 fix corner case in conditionals (#3669)
fixes #3668
2020-01-04 09:24:28 +08:00
Alex Lam S.L
fdc10086da fix corner case in reduce_vars (#3667)
fixes #3666
2020-01-03 19:28:47 +08:00
Alex Lam S.L
746f5f6c62 fix corner case in unused (#3665)
fixes #3664
2020-01-01 20:24:30 +08:00
Alex Lam S.L
d83d3d741a enhance unused (#3662) 2019-12-31 23:39:24 +08:00
Alex Lam S.L
99ac73a635 enhance booleans (#3661) 2019-12-31 13:10:05 +08:00
Alex Lam S.L
a2e4c2fd97 enhance evaluate (#3660) 2019-12-31 11:51:21 +08:00
Alex Lam S.L
94785e8e14 fix corner case in booleans (#3659)
fixes #3658
2019-12-31 09:57:35 +08:00
Alex Lam S.L
4dbdac9c31 enhance booleans (#3657) 2019-12-30 22:41:11 +08:00
Alex Lam S.L
78c8efd851 fix corner case in evaluate (#3656)
fixes #3655
2019-12-29 21:16:53 +08:00
Alex Lam S.L
af310ba2d0 fix corner case in evaluate (#3654)
fixes #3653
2019-12-29 02:50:57 +00:00
Alex Lam S.L
2f3930d1b9 fix corner case in collapse_vars (#3652)
fixes #3651
2019-12-29 00:57:59 +00:00
Alex Lam S.L
d1a78920d9 workaround firefox asm.js quirks (#3650)
fixes #3636
2019-12-28 23:14:53 +00:00
Alex Lam S.L
d9cd3d33c8 enhance evaluate (#3649) 2019-12-28 20:26:15 +00:00
Alex Lam S.L
22b47cdd63 improve unicode handling (#3648) 2019-12-28 18:06:51 +00:00
Alex Lam S.L
4cf612dc9f increase mocha default timeout (#3647)
closes #3640
2019-12-28 02:32:22 +00:00
Alex Lam S.L
a19d31dd33 fix corner case in unsafe (#3646) 2019-12-27 14:24:54 +00:00
Alex Lam S.L
01d6e0f223 v3.7.3 2019-12-27 06:11:29 +08:00
Alex Lam S.L
ab050e7a94 fix corner case in directives (#3645) 2019-12-25 00:55:39 +00:00
Alex Lam S.L
75aa6ef848 enhance conditionals (#3643) 2019-12-22 04:29:32 +00:00
Alex Lam S.L
519a00bd8a fix corner case in collapse_vars (#3642)
fixes #3641
2019-12-22 01:08:56 +00:00
Alex Lam S.L
3ff0feddee suppress false positives from fuzzer (#3638) 2019-12-16 17:32:47 +02:00
Alex Lam S.L
74396acc86 fix corner case in loops (#3635)
fixes #3634
2019-12-11 06:39:46 +08:00
Alex Lam S.L
036bca980c enhance loops (#3633) 2019-12-10 12:57:47 +00:00
Alex Lam S.L
18c2b1841b fix corner case in reduce_vars (#3632)
fixes #3631
2019-12-10 09:45:51 +00:00
34 changed files with 1953 additions and 597 deletions

View File

@@ -87,6 +87,7 @@ a double dash to prevent input files being used as option arguments:
`wrap_iife` Wrap IIFEs in parenthesis. Note: you may `wrap_iife` Wrap IIFEs in parenthesis. Note: you may
want to disable `negate_iife` under want to disable `negate_iife` under
compressor options. compressor options.
-O, --output-opts [options] Specify output options (`beautify` disabled by default).
-o, --output <file> Output file path (default STDOUT). Specify `ast` or -o, --output <file> Output file path (default STDOUT). Specify `ast` or
`spidermonkey` to write UglifyJS or SpiderMonkey AST `spidermonkey` to write UglifyJS or SpiderMonkey AST
as JSON to STDOUT respectively. as JSON to STDOUT respectively.

View File

@@ -36,6 +36,7 @@ program.option("-c, --compress [options]", "Enable compressor/specify compressor
program.option("-m, --mangle [options]", "Mangle names/specify mangler options.", parse_js()); program.option("-m, --mangle [options]", "Mangle names/specify mangler options.", parse_js());
program.option("--mangle-props [options]", "Mangle properties/specify mangler options.", parse_js()); program.option("--mangle-props [options]", "Mangle properties/specify mangler options.", parse_js());
program.option("-b, --beautify [options]", "Beautify output/specify output options.", parse_js()); program.option("-b, --beautify [options]", "Beautify output/specify output options.", parse_js());
program.option("-O, --output-opts [options]", "Output options (beautify disabled).", parse_js());
program.option("-o, --output <file>", "Output file (default STDOUT)."); program.option("-o, --output <file>", "Output file (default STDOUT).");
program.option("--comments [filter]", "Preserve copyright comments in the output."); program.option("--comments [filter]", "Preserve copyright comments in the output.");
program.option("--config-file <file>", "Read minify() options from JSON file."); program.option("--config-file <file>", "Read minify() options from JSON file.");
@@ -59,7 +60,7 @@ if (program.configFile) {
if (options.mangle && options.mangle.properties && options.mangle.properties.regex) { if (options.mangle && options.mangle.properties && options.mangle.properties.regex) {
options.mangle.properties.regex = UglifyJS.parse(options.mangle.properties.regex, { options.mangle.properties.regex = UglifyJS.parse(options.mangle.properties.regex, {
expression: true expression: true
}).getValue(); }).value;
} }
} }
if (!program.output && program.sourceMap && program.sourceMap.url != "inline") { if (!program.output && program.sourceMap && program.sourceMap.url != "inline") {
@@ -93,6 +94,10 @@ if (program.beautify) {
options.output.beautify = true; options.output.beautify = true;
} }
} }
if (program.outputOpts) {
if (program.beautify) fatal("--beautify cannot be used with --output-opts");
options.output = typeof program.outputOpts == "object" ? program.outputOpts : {};
}
if (program.comments) { if (program.comments) {
if (typeof options.output != "object") options.output = {}; if (typeof options.output != "object") options.output = {};
options.output.comments = typeof program.comments == "string" ? program.comments : "some"; options.output.comments = typeof program.comments == "string" ? program.comments : "some";
@@ -370,7 +375,7 @@ function parse_js(flag) {
if (!(node instanceof UglifyJS.AST_Sequence)) throw node; if (!(node instanceof UglifyJS.AST_Sequence)) throw node;
function to_string(value) { function to_string(value) {
return value instanceof UglifyJS.AST_Constant ? value.getValue() : value.print_to_string({ return value instanceof UglifyJS.AST_Constant ? value.value : value.print_to_string({
quote_keys: true quote_keys: true
}); });
} }

View File

@@ -618,7 +618,7 @@ var AST_PropAccess = DEFNODE("PropAccess", "expression property", {
getProperty: function() { getProperty: function() {
var p = this.property; var p = this.property;
if (p instanceof AST_Constant) { if (p instanceof AST_Constant) {
return p.getValue(); return p.value;
} }
if (p instanceof AST_UnaryPrefix if (p instanceof AST_UnaryPrefix
&& p.operator == "void" && p.operator == "void"
@@ -824,9 +824,6 @@ var AST_This = DEFNODE("This", null, {
var AST_Constant = DEFNODE("Constant", null, { var AST_Constant = DEFNODE("Constant", null, {
$documentation: "Base class for all constants", $documentation: "Base class for all constants",
getValue: function() {
return this.value;
}
}); });
var AST_String = DEFNODE("String", "value quote", { var AST_String = DEFNODE("String", "value quote", {
@@ -967,11 +964,13 @@ TreeWalker.prototype = {
in_boolean_context: function() { in_boolean_context: function() {
var self = this.self(); var self = this.self();
for (var i = 0, p; p = this.parent(i); i++) { for (var i = 0, p; p = this.parent(i); i++) {
if (p instanceof AST_SimpleStatement if (p instanceof AST_Conditional && p.condition === self
|| p instanceof AST_Conditional && p.condition === self
|| p instanceof AST_DWLoop && p.condition === self || p instanceof AST_DWLoop && p.condition === self
|| p instanceof AST_For && p.condition === self || p instanceof AST_For && p.condition === self
|| p instanceof AST_If && p.condition === self || p instanceof AST_If && p.condition === self
|| p instanceof AST_Return && p.in_bool
|| p instanceof AST_Sequence && p.tail_node() !== self
|| p instanceof AST_SimpleStatement
|| p instanceof AST_UnaryPrefix && p.operator == "!" && p.expression === self) { || p instanceof AST_UnaryPrefix && p.operator == "!" && p.expression === self) {
return true; return true;
} }

View File

@@ -209,6 +209,7 @@ merge(Compressor.prototype, {
if (is_scope) { if (is_scope) {
node.hoist_properties(this); node.hoist_properties(this);
node.hoist_declarations(this); node.hoist_declarations(this);
node.process_boolean_returns(this);
} }
// Before https://github.com/mishoo/UglifyJS2/pull/1602 AST_Node.optimize() // Before https://github.com/mishoo/UglifyJS2/pull/1602 AST_Node.optimize()
// would call AST_Node.transform() if a different instance of AST_Node is // would call AST_Node.transform() if a different instance of AST_Node is
@@ -241,7 +242,7 @@ merge(Compressor.prototype, {
return this.TYPE == node.TYPE && this.print_to_string() == node.print_to_string(); return this.TYPE == node.TYPE && this.print_to_string() == node.print_to_string();
}); });
AST_Scope.DEFMETHOD("process_expression", function(insert, compressor) { AST_Scope.DEFMETHOD("process_expression", function(insert, transform) {
var self = this; var self = this;
var tt = new TreeTransformer(function(node) { var tt = new TreeTransformer(function(node) {
if (insert && node instanceof AST_SimpleStatement) { if (insert && node instanceof AST_SimpleStatement) {
@@ -250,13 +251,7 @@ merge(Compressor.prototype, {
}); });
} }
if (!insert && node instanceof AST_Return) { if (!insert && node instanceof AST_Return) {
if (compressor) { return transform ? transform(node) : make_node(AST_SimpleStatement, node, {
var value = node.value && node.value.drop_side_effect_free(compressor, true);
return value ? make_node(AST_SimpleStatement, node, {
body: value
}) : make_node(AST_EmptyStatement, node);
}
return make_node(AST_SimpleStatement, node, {
body: node.value || make_node(AST_UnaryPrefix, node, { body: node.value || make_node(AST_UnaryPrefix, node, {
operator: "void", operator: "void",
expression: make_node(AST_Number, node, { expression: make_node(AST_Number, node, {
@@ -361,6 +356,7 @@ merge(Compressor.prototype, {
function reset_def(tw, compressor, def) { function reset_def(tw, compressor, def) {
def.assignments = 0; def.assignments = 0;
def.bool_fn = 0;
def.chained = false; def.chained = false;
def.cross_loop = false; def.cross_loop = false;
def.direct_access = false; def.direct_access = false;
@@ -596,14 +592,22 @@ merge(Compressor.prototype, {
def(AST_Call, function(tw, descend) { def(AST_Call, function(tw, descend) {
tw.find_parent(AST_Scope).may_call_this(); tw.find_parent(AST_Scope).may_call_this();
var exp = this.expression; var exp = this.expression;
if (!(exp instanceof AST_SymbolRef)) return; if (exp instanceof AST_SymbolRef) {
var def = exp.definition(); var def = exp.definition();
if (this.TYPE == "Call" && tw.in_boolean_context()) def.bool_fn++;
if (!(def.fixed instanceof AST_Defun)) return; if (!(def.fixed instanceof AST_Defun)) return;
var defun = mark_defun(tw, def); var defun = mark_defun(tw, def);
if (!defun) return; if (!defun) return;
descend(); descend();
defun.walk(tw); defun.walk(tw);
return true; return true;
} else if (this.TYPE == "Call"
&& exp instanceof AST_Assign
&& exp.operator == "="
&& exp.left instanceof AST_SymbolRef
&& tw.in_boolean_context()) {
exp.left.definition().bool_fn++;
}
}); });
def(AST_Case, function(tw) { def(AST_Case, function(tw) {
push(tw); push(tw);
@@ -648,7 +652,7 @@ merge(Compressor.prototype, {
tw.in_loop = this; tw.in_loop = this;
push(tw); push(tw);
this.body.walk(tw); this.body.walk(tw);
if (has_break_or_continue(this)) { if (has_break_or_continue(this, tw.parent())) {
pop(tw); pop(tw);
push(tw); push(tw);
} }
@@ -665,7 +669,7 @@ merge(Compressor.prototype, {
if (this.condition) this.condition.walk(tw); if (this.condition) this.condition.walk(tw);
this.body.walk(tw); this.body.walk(tw);
if (this.step) { if (this.step) {
if (has_break_or_continue(this)) { if (has_break_or_continue(this, tw.parent())) {
pop(tw); pop(tw);
push(tw); push(tw);
} }
@@ -1352,7 +1356,10 @@ merge(Compressor.prototype, {
return node.operator != "=" && lhs.equivalent_to(node.left); return node.operator != "=" && lhs.equivalent_to(node.left);
} }
if (node instanceof AST_Call) { if (node instanceof AST_Call) {
return lhs instanceof AST_PropAccess && lhs.equivalent_to(node.expression); if (!(lhs instanceof AST_PropAccess)) return false;
if (!lhs.equivalent_to(node.expression)) return false;
var rhs = get_rvalue(candidate);
return !(rhs instanceof AST_Function && !rhs.contains_this());
} }
if (node instanceof AST_Debugger) return true; if (node instanceof AST_Debugger) return true;
if (node instanceof AST_Defun) return funarg && lhs.name === node.name.name; if (node instanceof AST_Defun) return funarg && lhs.name === node.name.name;
@@ -1363,7 +1370,7 @@ merge(Compressor.prototype, {
if (replace_all) return false; if (replace_all) return false;
return node instanceof AST_SymbolRef return node instanceof AST_SymbolRef
&& !node.is_declared(compressor) && !node.is_declared(compressor)
&& !(parent instanceof AST_Assign && parent.left === node); && !(parent instanceof AST_Assign && parent.operator == "=" && parent.left === node);
} }
function in_conditional(node, parent) { function in_conditional(node, parent) {
@@ -1569,6 +1576,7 @@ merge(Compressor.prototype, {
var parent = scanner.parent(level); var parent = scanner.parent(level);
if (parent instanceof AST_Array) return find_stop_value(parent, level + 1); if (parent instanceof AST_Array) return find_stop_value(parent, level + 1);
if (parent instanceof AST_Assign) { if (parent instanceof AST_Assign) {
if (may_throw(parent)) return node;
if (parent.left instanceof AST_SymbolRef) { if (parent.left instanceof AST_SymbolRef) {
var name = parent.left.name; var name = parent.left.name;
if (lhs.name == name) return node; if (lhs.name == name) return node;
@@ -1647,7 +1655,9 @@ merge(Compressor.prototype, {
if (is_last_node(node, parent)) return node; if (is_last_node(node, parent)) return node;
if (in_conditional(node, parent)) return node; if (in_conditional(node, parent)) return node;
if (parent instanceof AST_Array) return find_stop_unused(parent, level + 1); if (parent instanceof AST_Array) return find_stop_unused(parent, level + 1);
if (parent instanceof AST_Assign) return find_stop_unused(parent, level + 1); if (parent instanceof AST_Assign) {
return may_throw(parent) ? node : find_stop_unused(parent, level + 1);
}
if (parent instanceof AST_Binary) return find_stop_unused(parent, level + 1); if (parent instanceof AST_Binary) return find_stop_unused(parent, level + 1);
if (parent instanceof AST_Call) return find_stop_unused(parent, level + 1); if (parent instanceof AST_Call) return find_stop_unused(parent, level + 1);
if (parent instanceof AST_Case) return find_stop_unused(parent, level + 1); if (parent instanceof AST_Case) return find_stop_unused(parent, level + 1);
@@ -2014,9 +2024,10 @@ merge(Compressor.prototype, {
if (stat instanceof AST_If && stat.body instanceof AST_Return) { if (stat instanceof AST_If && stat.body instanceof AST_Return) {
var value = stat.body.value; var value = stat.body.value;
var in_bool = stat.body.in_bool || next instanceof AST_Return && next.in_bool;
//--- //---
// pretty silly case, but: // pretty silly case, but:
// if (foo()) return; return; ==> foo(); return; // if (foo()) return; return; => foo(); return;
if (!value && !stat.alternative if (!value && !stat.alternative
&& (in_lambda && !next || next instanceof AST_Return && !next.value)) { && (in_lambda && !next || next instanceof AST_Return && !next.value)) {
CHANGED = true; CHANGED = true;
@@ -2026,8 +2037,8 @@ merge(Compressor.prototype, {
continue; continue;
} }
//--- //---
// if (foo()) return x; return y; ==> return foo() ? x : y; // if (foo()) return x; return y; => return foo() ? x : y;
if (value && !stat.alternative && next instanceof AST_Return && next.value) { if ((in_bool || value) && !stat.alternative && next instanceof AST_Return) {
CHANGED = true; CHANGED = true;
stat = stat.clone(); stat = stat.clone();
stat.alternative = next; stat.alternative = next;
@@ -2036,21 +2047,18 @@ merge(Compressor.prototype, {
continue; continue;
} }
//--- //---
// if (foo()) return x; [ return ; ] ==> return foo() ? x : undefined; // if (foo()) return x; [ return ; ] => return foo() ? x : undefined;
if (value && !stat.alternative if (!stat.alternative && !next && in_lambda && (in_bool || value && multiple_if_returns)) {
&& (!next && in_lambda && multiple_if_returns
|| next instanceof AST_Return)) {
CHANGED = true; CHANGED = true;
stat = stat.clone(); stat = stat.clone();
stat.alternative = next || make_node(AST_Return, stat, { stat.alternative = make_node(AST_Return, stat, {
value: null value: null
}); });
statements.splice(i, 1, stat.transform(compressor)); statements.splice(i, 1, stat.transform(compressor));
if (next) statements.splice(j, 1);
continue; continue;
} }
//--- //---
// if (a) return b; if (c) return d; e; ==> return a ? b : c ? d : void e; // if (a) return b; if (c) return d; e; => return a ? b : c ? d : void e;
// //
// if sequences is not enabled, this can lead to an endless loop (issue #866). // if sequences is not enabled, this can lead to an endless loop (issue #866).
// however, with sequences on this helps producing slightly better output for // however, with sequences on this helps producing slightly better output for
@@ -2869,9 +2877,11 @@ merge(Compressor.prototype, {
} }
function convert_to_predicate(obj) { function convert_to_predicate(obj) {
var map = Object.create(null);
Object.keys(obj).forEach(function(key) { Object.keys(obj).forEach(function(key) {
obj[key] = makePredicate(obj[key]); map[key] = makePredicate(obj[key]);
}); });
return map;
} }
AST_Lambda.DEFMETHOD("first_statement", function() { AST_Lambda.DEFMETHOD("first_statement", function() {
@@ -2894,7 +2904,7 @@ merge(Compressor.prototype, {
"toString", "toString",
"valueOf", "valueOf",
]; ];
var native_fns = { var native_fns = convert_to_predicate({
Array: [ Array: [
"indexOf", "indexOf",
"join", "join",
@@ -2930,9 +2940,8 @@ merge(Compressor.prototype, {
"toUpperCase", "toUpperCase",
"trim", "trim",
].concat(object_fns), ].concat(object_fns),
}; });
convert_to_predicate(native_fns); var static_fns = convert_to_predicate({
var static_fns = {
Array: [ Array: [
"isArray", "isArray",
], ],
@@ -2972,8 +2981,7 @@ merge(Compressor.prototype, {
String: [ String: [
"fromCharCode", "fromCharCode",
], ],
}; });
convert_to_predicate(static_fns);
// methods to evaluate a constant expression // methods to evaluate a constant expression
(function(def) { (function(def) {
@@ -3011,7 +3019,7 @@ merge(Compressor.prototype, {
def(AST_Lambda, return_this); def(AST_Lambda, return_this);
def(AST_Node, return_this); def(AST_Node, return_this);
def(AST_Constant, function() { def(AST_Constant, function() {
return this.getValue(); return this.value;
}); });
def(AST_Function, function(compressor) { def(AST_Function, function(compressor) {
if (compressor.option("unsafe")) { if (compressor.option("unsafe")) {
@@ -3196,7 +3204,7 @@ merge(Compressor.prototype, {
Object: Object, Object: Object,
String: String, String: String,
}; };
var static_values = { var static_values = convert_to_predicate({
Math: [ Math: [
"E", "E",
"LN10", "LN10",
@@ -3214,8 +3222,7 @@ merge(Compressor.prototype, {
"NEGATIVE_INFINITY", "NEGATIVE_INFINITY",
"POSITIVE_INFINITY", "POSITIVE_INFINITY",
], ],
}; });
convert_to_predicate(static_values);
var regexp_props = makePredicate("global ignoreCase multiline source"); var regexp_props = makePredicate("global ignoreCase multiline source");
def(AST_PropAccess, function(compressor, cached, depth) { def(AST_PropAccess, function(compressor, cached, depth) {
if (compressor.option("unsafe")) { if (compressor.option("unsafe")) {
@@ -3633,7 +3640,7 @@ merge(Compressor.prototype, {
return !this.is_declared(compressor); return !this.is_declared(compressor);
}); });
def(AST_Try, function(compressor) { def(AST_Try, function(compressor) {
return this.bcatch ? this.bcatch.may_throw(compressor) : any(this.body, compressor) return (this.bcatch ? this.bcatch.may_throw(compressor) : any(this.body, compressor))
|| this.bfinally && this.bfinally.may_throw(compressor); || this.bfinally && this.bfinally.may_throw(compressor);
}); });
def(AST_Unary, function(compressor) { def(AST_Unary, function(compressor) {
@@ -3875,7 +3882,7 @@ merge(Compressor.prototype, {
}); });
return true; return true;
} }
return scan_ref_scoped(node, descend); return scan_ref_scoped(node, descend, true);
}); });
self.walk(tw); self.walk(tw);
// pass 2: for every used symbol we need to walk its // pass 2: for every used symbol we need to walk its
@@ -3909,6 +3916,11 @@ merge(Compressor.prototype, {
if (node instanceof AST_Assign) { if (node instanceof AST_Assign) {
if (!in_use || node.left === sym && def.id in fixed_ids && fixed_ids[def.id] !== node) { if (!in_use || node.left === sym && def.id in fixed_ids && fixed_ids[def.id] !== node) {
value = get_rhs(node); value = get_rhs(node);
if (node.write_only === true) {
value = value.drop_side_effect_free(compressor) || make_node(AST_Number, node, {
value: 0
});
}
} }
} else if (!in_use) { } else if (!in_use) {
value = make_node(AST_Number, node, { value = make_node(AST_Number, node, {
@@ -4178,7 +4190,7 @@ merge(Compressor.prototype, {
return rhs.right.has_side_effects(compressor) ? rhs : rhs.right; return rhs.right.has_side_effects(compressor) ? rhs : rhs.right;
} }
function scan_ref_scoped(node, descend) { function scan_ref_scoped(node, descend, init) {
var node_def, props = [], sym = assign_as_unused(node, props); var node_def, props = [], sym = assign_as_unused(node, props);
if (sym && self.variables.get(sym.name) === (node_def = sym.definition())) { if (sym && self.variables.get(sym.name) === (node_def = sym.definition())) {
props.forEach(function(prop) { props.forEach(function(prop) {
@@ -4187,7 +4199,11 @@ merge(Compressor.prototype, {
if (node instanceof AST_Assign) { if (node instanceof AST_Assign) {
if (node.write_only === "p" && node.right.may_throw_on_access(compressor)) return; if (node.write_only === "p" && node.right.may_throw_on_access(compressor)) return;
var right = get_rhs(node); var right = get_rhs(node);
if (init && node.write_only === true && node_def.scope === self && !right.has_side_effects(compressor)) {
initializations.add(node_def.id, right);
} else {
right.walk(tw); right.walk(tw);
}
if (node.left === sym) { if (node.left === sym) {
if (!node_def.chained && sym.fixed_value(true) === right) { if (!node_def.chained && sym.fixed_value(true) === right) {
fixed_ids[node_def.id] = node; fixed_ids[node_def.id] = node;
@@ -4346,6 +4362,96 @@ merge(Compressor.prototype, {
self.body = dirs.concat(hoisted, self.body); self.body = dirs.concat(hoisted, self.body);
}); });
function scan_local_returns(fn, transform) {
fn.walk(new TreeWalker(function(node) {
if (node instanceof AST_Return) {
transform(node);
return true;
}
if (node instanceof AST_Scope && node !== fn) return true;
}));
}
function map_bool_returns(fn) {
var map = Object.create(null);
scan_local_returns(fn, function(node) {
var value = node.value;
if (value) value = value.tail_node();
if (value instanceof AST_SymbolRef) {
var id = value.definition().id;
map[id] = (map[id] || 0) + 1;
}
});
return map;
}
function all_bool(def, bool_returns, compressor) {
return def.bool_fn + (bool_returns[def.id] || 0) === def.references.length
&& !compressor.exposed(def);
}
function process_boolean_returns(fn, compressor) {
scan_local_returns(fn, function(node) {
node.in_bool = true;
var value = node.value;
if (value) {
var ev = value.is_truthy() || value.tail_node().evaluate(compressor);
if (!ev) {
value = value.drop_side_effect_free(compressor);
if (node.value !== value) node.value = value ? make_sequence(node.value, [
value,
make_node(AST_Number, node.value, {
value: 0
})
]) : null;
} else if (ev && !(ev instanceof AST_Node)) {
value = value.drop_side_effect_free(compressor);
if (node.value !== value) node.value = value ? make_sequence(node.value, [
value,
make_node(AST_Number, node.value, {
value: 1
})
]) : make_node(AST_Number, node.value, {
value: 1
});
}
}
});
}
AST_Scope.DEFMETHOD("process_boolean_returns", noop);
AST_Defun.DEFMETHOD("process_boolean_returns", function(compressor) {
if (!compressor.option("booleans")) return;
var bool_returns = map_bool_returns(this);
if (!all_bool(this.name.definition(), bool_returns, compressor)) return;
process_boolean_returns(this, compressor);
});
AST_Function.DEFMETHOD("process_boolean_returns", function(compressor) {
if (!compressor.option("booleans")) return;
var bool_returns = map_bool_returns(this);
if (this.name && !all_bool(this.name.definition(), bool_returns, compressor)) return;
var parent = compressor.parent();
if (parent instanceof AST_Assign) {
if (parent.operator != "=") return;
var sym = parent.left;
if (!(sym instanceof AST_SymbolRef)) return;
if (!all_bool(sym.definition(), bool_returns, compressor)) return;
} else if (parent instanceof AST_Call && parent.expression !== this) {
var exp = parent.expression;
if (exp instanceof AST_SymbolRef) exp = exp.fixed_value();
if (!(exp instanceof AST_Lambda)) return;
if (exp.uses_arguments || exp.pinned()) return;
var sym = exp.argnames[parent.args.indexOf(this)];
if (sym && !all_bool(sym.definition(), bool_returns, compressor)) return;
} else if (parent.TYPE == "Call") {
compressor.pop();
var in_bool = compressor.in_boolean_context();
compressor.push(this);
if (!in_bool) return;
} else return;
process_boolean_returns(this, compressor);
});
AST_Scope.DEFMETHOD("var_names", function() { AST_Scope.DEFMETHOD("var_names", function() {
var var_names = this._var_names; var var_names = this._var_names;
if (!var_names) { if (!var_names) {
@@ -4558,7 +4664,12 @@ merge(Compressor.prototype, {
} }
if (exp instanceof AST_Function && (!exp.name || !exp.name.definition().references.length)) { if (exp instanceof AST_Function && (!exp.name || !exp.name.definition().references.length)) {
var node = this.clone(); var node = this.clone();
exp.process_expression(false, compressor); exp.process_expression(false, function(node) {
var value = node.value && node.value.drop_side_effect_free(compressor, true);
return value ? make_node(AST_SimpleStatement, node, {
body: value
}) : make_node(AST_EmptyStatement, node);
});
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) {
node.value = node.value.drop_side_effect_free(compressor); node.value = node.value.drop_side_effect_free(compressor);
@@ -4718,8 +4829,11 @@ merge(Compressor.prototype, {
}); });
function if_break_in_loop(self, compressor) { function if_break_in_loop(self, compressor) {
var first = self.body instanceof AST_BlockStatement ? self.body.body[0] : self.body; var first = first_statement(self.body);
if (compressor.option("dead_code") && is_break(first)) { if (compressor.option("dead_code")
&& (first instanceof AST_Break
|| first instanceof AST_Continue && external_target(first)
|| first instanceof AST_Exit)) {
var body = []; var body = [];
if (self.init instanceof AST_Statement) { if (self.init instanceof AST_Statement) {
body.push(self.init); body.push(self.init);
@@ -4728,10 +4842,19 @@ merge(Compressor.prototype, {
body: self.init body: self.init
})); }));
} }
if (self.condition) { var retain = external_target(first) || first instanceof AST_Exit;
if (self.condition && retain) {
body.push(make_node(AST_If, self, {
condition: self.condition,
body: first,
alternative: null
}));
} else if (self.condition) {
body.push(make_node(AST_SimpleStatement, self.condition, { body.push(make_node(AST_SimpleStatement, self.condition, {
body: self.condition body: self.condition
})); }));
} else if (retain) {
body.push(first);
} }
extract_declarations_from_unreachable_code(self.body, body); extract_declarations_from_unreachable_code(self.body, body);
return make_node(AST_BlockStatement, self, { return make_node(AST_BlockStatement, self, {
@@ -4739,7 +4862,8 @@ merge(Compressor.prototype, {
}); });
} }
if (first instanceof AST_If) { if (first instanceof AST_If) {
if (is_break(first.body)) { var ab = first_statement(first.body);
if (ab instanceof AST_Break && !external_target(ab)) {
if (self.condition) { if (self.condition) {
self.condition = make_node(AST_Binary, self.condition, { self.condition = make_node(AST_Binary, self.condition, {
left: self.condition, left: self.condition,
@@ -4749,8 +4873,12 @@ merge(Compressor.prototype, {
} else { } else {
self.condition = first.condition.negate(compressor); self.condition = first.condition.negate(compressor);
} }
drop_it(first.alternative); var body = as_statement_array(first.alternative);
} else if (is_break(first.alternative)) { extract_declarations_from_unreachable_code(first.body, body);
return drop_it(body);
}
ab = first_statement(first.alternative);
if (ab instanceof AST_Break && !external_target(ab)) {
if (self.condition) { if (self.condition) {
self.condition = make_node(AST_Binary, self.condition, { self.condition = make_node(AST_Binary, self.condition, {
left: self.condition, left: self.condition,
@@ -4760,18 +4888,22 @@ merge(Compressor.prototype, {
} else { } else {
self.condition = first.condition; self.condition = first.condition;
} }
drop_it(first.body); var body = as_statement_array(first.body);
extract_declarations_from_unreachable_code(first.alternative, body);
return drop_it(body);
} }
} }
return self; return self;
function is_break(node) { function first_statement(body) {
return node instanceof AST_Break return body instanceof AST_BlockStatement ? body.body[0] : body;
&& compressor.loopcontrol_target(node) === compressor.self(); }
function external_target(node) {
return compressor.loopcontrol_target(node) !== compressor.self();
} }
function drop_it(rest) { function drop_it(rest) {
rest = as_statement_array(rest);
if (self.body instanceof AST_BlockStatement) { if (self.body instanceof AST_BlockStatement) {
self.body = self.body.clone(); self.body = self.body.clone();
self.body.body = rest.concat(self.body.body.slice(1)); self.body.body = rest.concat(self.body.body.slice(1));
@@ -4781,7 +4913,7 @@ merge(Compressor.prototype, {
body: rest body: rest
}).transform(compressor); }).transform(compressor);
} }
self = if_break_in_loop(self, compressor); return if_break_in_loop(self, compressor);
} }
} }
@@ -4854,7 +4986,7 @@ merge(Compressor.prototype, {
var sym = condition.right.expression; var sym = condition.right.expression;
if (!is_undeclared_ref(sym)) return; if (!is_undeclared_ref(sym)) return;
var body; var body;
var undef = condition.left.getValue() == "undefined"; var undef = condition.left.value == "undefined";
switch (condition.operator) { switch (condition.operator) {
case "==": case "==":
body = undef ? alternative : consequent; body = undef ? alternative : consequent;
@@ -4982,13 +5114,17 @@ merge(Compressor.prototype, {
if (self.body instanceof AST_Exit if (self.body instanceof AST_Exit
&& self.alternative instanceof AST_Exit && self.alternative instanceof AST_Exit
&& self.body.TYPE == self.alternative.TYPE) { && self.body.TYPE == self.alternative.TYPE) {
return make_node(self.body.CTOR, self, { var exit = make_node(self.body.CTOR, self, {
value: make_node(AST_Conditional, self, { value: make_node(AST_Conditional, self, {
condition : self.condition, condition : self.condition,
consequent : self.body.value || make_node(AST_Undefined, self.body), consequent : self.body.value || make_node(AST_Undefined, self.body).transform(compressor),
alternative : self.alternative.value || make_node(AST_Undefined, self.alternative) alternative : self.alternative.value || make_node(AST_Undefined, self.alternative).transform(compressor)
}).transform(compressor) })
}).optimize(compressor); });
if (exit instanceof AST_Return) {
exit.in_bool = self.body.in_bool || self.alternative.in_bool;
}
return exit;
} }
if (self.body instanceof AST_If if (self.body instanceof AST_If
&& !self.body.alternative && !self.body.alternative
@@ -5290,7 +5426,7 @@ merge(Compressor.prototype, {
if (self.args.length == 1) { if (self.args.length == 1) {
var first = self.args[0]; var first = self.args[0];
if (first instanceof AST_Number) try { if (first instanceof AST_Number) try {
var length = first.getValue(); var length = first.value;
if (length > 6) break; if (length > 6) break;
var elements = Array(length); var elements = Array(length);
for (var i = 0; i < length; i++) elements[i] = make_node(AST_Hole, self); for (var i = 0; i < length; i++) elements[i] = make_node(AST_Hole, self);
@@ -5903,7 +6039,7 @@ merge(Compressor.prototype, {
} else if (compressor.in_boolean_context()) switch (self.operator) { } else if (compressor.in_boolean_context()) switch (self.operator) {
case "!": case "!":
if (e instanceof AST_UnaryPrefix && e.operator == "!") { if (e instanceof AST_UnaryPrefix && e.operator == "!") {
// !!foo ==> foo, if we're in boolean context // !!foo => foo, if we're in boolean context
return e.expression; return e.expression;
} }
if (e instanceof AST_Binary) { if (e instanceof AST_Binary) {
@@ -5975,6 +6111,7 @@ merge(Compressor.prototype, {
}); });
var indexFns = makePredicate("indexOf lastIndexOf"); var indexFns = makePredicate("indexOf lastIndexOf");
var minus_zero_op = makePredicate("- * / %");
var commutativeOperators = makePredicate("== === != !== * & | ^"); var commutativeOperators = makePredicate("== === != !== * & | ^");
function is_object(node) { function is_object(node) {
return node instanceof AST_Array return node instanceof AST_Array
@@ -6048,7 +6185,7 @@ merge(Compressor.prototype, {
// "undefined" == typeof x => undefined === x // "undefined" == typeof x => undefined === x
else if (compressor.option("typeofs") else if (compressor.option("typeofs")
&& self.left instanceof AST_String && self.left instanceof AST_String
&& self.left.getValue() == "undefined" && self.left.value == "undefined"
&& self.right instanceof AST_UnaryPrefix && self.right instanceof AST_UnaryPrefix
&& self.right.operator == "typeof") { && self.right.operator == "typeof") {
var expr = self.right.expression; var expr = self.right.expression;
@@ -6120,7 +6257,7 @@ merge(Compressor.prototype, {
} }
break; break;
case "==": case "==":
if (self.left instanceof AST_String && self.left.getValue() == "" && self.right.is_string(compressor)) { if (self.left instanceof AST_String && self.left.value == "" && self.right.is_string(compressor)) {
return make_node(AST_UnaryPrefix, self, { return make_node(AST_UnaryPrefix, self, {
operator: "!", operator: "!",
expression: self.right expression: self.right
@@ -6128,7 +6265,7 @@ merge(Compressor.prototype, {
} }
break; break;
case "!=": case "!=":
if (self.left instanceof AST_String && self.left.getValue() == "" && self.right.is_string(compressor)) { if (self.left instanceof AST_String && self.left.value == "" && self.right.is_string(compressor)) {
return self.right.optimize(compressor); return self.right.optimize(compressor);
} }
break; break;
@@ -6149,19 +6286,19 @@ merge(Compressor.prototype, {
} }
if (self.operator == "+") { if (self.operator == "+") {
if (self.right instanceof AST_String if (self.right instanceof AST_String
&& self.right.getValue() == "" && self.right.value == ""
&& self.left.is_string(compressor)) { && self.left.is_string(compressor)) {
return self.left.optimize(compressor); return self.left.optimize(compressor);
} }
if (self.left instanceof AST_String if (self.left instanceof AST_String
&& self.left.getValue() == "" && self.left.value == ""
&& self.right.is_string(compressor)) { && self.right.is_string(compressor)) {
return self.right.optimize(compressor); return self.right.optimize(compressor);
} }
if (self.left instanceof AST_Binary if (self.left instanceof AST_Binary
&& self.left.operator == "+" && self.left.operator == "+"
&& self.left.left instanceof AST_String && self.left.left instanceof AST_String
&& self.left.left.getValue() == "" && self.left.left.value == ""
&& self.right.is_string(compressor)) { && self.right.is_string(compressor)) {
self.left = self.left.right; self.left = self.left.right;
return self.optimize(compressor); return self.optimize(compressor);
@@ -6194,9 +6331,9 @@ merge(Compressor.prototype, {
return self.left.optimize(compressor); return self.left.optimize(compressor);
} }
} }
// x || false && y ---> x ? y : false // (x || false) && y => x ? y : false
if (self.left.operator == "||") { if (self.left.operator == "||") {
var lr = self.left.right.evaluate(compressor); var lr = self.left.right.tail_node().evaluate(compressor);
if (!lr) return make_node(AST_Conditional, self, { if (!lr) return make_node(AST_Conditional, self, {
condition: self.left.left, condition: self.left.left,
consequent: self.right, consequent: self.right,
@@ -6228,8 +6365,9 @@ merge(Compressor.prototype, {
]).optimize(compressor); ]).optimize(compressor);
} else self.truthy = true; } else self.truthy = true;
} }
// x && true || y => x ? true : y
if (self.left.operator == "&&") { if (self.left.operator == "&&") {
var lr = self.left.right.evaluate(compressor); var lr = self.left.right.is_truthy() || self.left.right.tail_node().evaluate(compressor);
if (lr && !(lr instanceof AST_Node)) return make_node(AST_Conditional, self, { if (lr && !(lr instanceof AST_Node)) return make_node(AST_Conditional, self, {
condition: self.left.left, condition: self.left.left,
consequent: self.left.right, consequent: self.left.right,
@@ -6247,7 +6385,7 @@ merge(Compressor.prototype, {
self = make_node(AST_Binary, self, { self = make_node(AST_Binary, self, {
operator: "+", operator: "+",
left: make_node(AST_String, self.left, { left: make_node(AST_String, self.left, {
value: "" + self.left.getValue() + self.right.left.getValue(), value: "" + self.left.value + self.right.left.value,
start: self.left.start, start: self.left.start,
end: self.right.left.end end: self.right.left.end
}), }),
@@ -6264,7 +6402,7 @@ merge(Compressor.prototype, {
operator: "+", operator: "+",
left: self.left.left, left: self.left.left,
right: make_node(AST_String, self.right, { right: make_node(AST_String, self.right, {
value: "" + self.left.right.getValue() + self.right.getValue(), value: "" + self.left.right.value + self.right.value,
start: self.left.right.start, start: self.left.right.start,
end: self.right.end end: self.right.end
}) })
@@ -6285,7 +6423,7 @@ merge(Compressor.prototype, {
operator: "+", operator: "+",
left: self.left.left, left: self.left.left,
right: make_node(AST_String, self.left.right, { right: make_node(AST_String, self.left.right, {
value: "" + self.left.right.getValue() + self.right.left.getValue(), value: "" + self.left.right.value + self.right.left.value,
start: self.left.right.start, start: self.left.right.start,
end: self.right.left.end end: self.right.left.end
}) })
@@ -6368,8 +6506,8 @@ merge(Compressor.prototype, {
// a + +b => +b + a // a + +b => +b + a
if (self.operator != "-" if (self.operator != "-"
&& self.operator != "/" && self.operator != "/"
&& self.left.is_number(compressor) && (self.left.is_boolean(compressor) || self.left.is_number(compressor))
&& self.right.is_number(compressor) && (self.right.is_boolean(compressor) || self.right.is_number(compressor))
&& reversible() && reversible()
&& !(self.left instanceof AST_Binary && !(self.left instanceof AST_Binary
&& self.left.operator != self.operator && self.left.operator != self.operator
@@ -6394,7 +6532,9 @@ merge(Compressor.prototype, {
&& self.right.is_number(compressor) && self.right.is_number(compressor)
&& (self.operator != "+" && (self.operator != "+"
|| self.right.left.is_boolean(compressor) || self.right.left.is_boolean(compressor)
|| self.right.left.is_number(compressor))) { || self.right.left.is_number(compressor))
&& (self.right.left.is_constant_expression()
|| !self.right.right.has_side_effects(compressor))) {
self = make_node(AST_Binary, self, { self = make_node(AST_Binary, self, {
operator: align(self.operator, self.right.operator), operator: align(self.operator, self.right.operator),
left: make_node(AST_Binary, self.left, { left: make_node(AST_Binary, self.left, {
@@ -6432,6 +6572,57 @@ merge(Compressor.prototype, {
} }
break; break;
} }
if (self.left instanceof AST_Number && !self.right.is_constant()) switch (self.operator) {
// 0 + n => n
case "+":
if (self.left.value == 0) {
if (self.right.is_boolean(compressor)) return make_node(AST_UnaryPrefix, self, {
operator: "+",
expression: self.right
}).optimize(compressor);
if (self.right.is_number(compressor) && !may_be_minus_zero(self.right)) return self.right;
}
break;
// 1 * n => n
case "*":
if (self.left.value == 1) {
return self.right.is_number(compressor) ? self.right : make_node(AST_UnaryPrefix, self, {
operator: "+",
expression: self.right
}).optimize(compressor);
}
break;
}
if (self.right instanceof AST_Number && !self.left.is_constant()) switch (self.operator) {
// n + 0 => n
case "+":
if (self.right.value == 0) {
if (self.left.is_boolean(compressor)) return make_node(AST_UnaryPrefix, self, {
operator: "+",
expression: self.left
}).optimize(compressor);
if (self.left.is_number(compressor) && !may_be_minus_zero(self.left)) return self.left;
}
break;
// n - 0 => n
case "-":
if (self.right.value == 0) {
return self.left.is_number(compressor) ? self.left : make_node(AST_UnaryPrefix, self, {
operator: "+",
expression: self.left
}).optimize(compressor);
}
break;
// n / 1 => n
case "/":
if (self.right.value == 1) {
return self.left.is_number(compressor) ? self.left : make_node(AST_UnaryPrefix, self, {
operator: "+",
expression: self.left
}).optimize(compressor);
}
break;
}
} }
if (compressor.option("typeofs")) switch (self.operator) { if (compressor.option("typeofs")) switch (self.operator) {
case "&&": case "&&":
@@ -6447,7 +6638,7 @@ merge(Compressor.prototype, {
&& indexRight && indexRight
&& (self.operator == "==" || self.operator == "!=") && (self.operator == "==" || self.operator == "!=")
&& self.left instanceof AST_Number && self.left instanceof AST_Number
&& self.left.getValue() == 0) { && self.left.value == 0) {
return (self.operator == "==" ? make_node(AST_UnaryPrefix, self, { return (self.operator == "==" ? make_node(AST_UnaryPrefix, self, {
operator: "!", operator: "!",
expression: self.right expression: self.right
@@ -6476,10 +6667,10 @@ merge(Compressor.prototype, {
return node.optimize(compressor); 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
// "x" + (y + "z")==> "x" + y + "z" // "x" + (y + "z") => "x" + y + "z"
if (self.right instanceof AST_Binary if (self.right instanceof AST_Binary
&& self.right.operator == self.operator && self.right.operator == self.operator
&& (lazy_op[self.operator] && (lazy_op[self.operator]
@@ -6550,21 +6741,35 @@ merge(Compressor.prototype, {
switch (self.operator) { switch (self.operator) {
case "<=": case "<=":
// 0 <= array.indexOf(string) => !!~array.indexOf(string) // 0 <= array.indexOf(string) => !!~array.indexOf(string)
return indexRight && self.left instanceof AST_Number && self.left.getValue() == 0; return indexRight && self.left instanceof AST_Number && self.left.value == 0;
case "<": case "<":
// array.indexOf(string) < 0 => !~array.indexOf(string) // array.indexOf(string) < 0 => !~array.indexOf(string)
if (indexLeft && self.right instanceof AST_Number && self.right.getValue() == 0) return true; if (indexLeft && self.right instanceof AST_Number && self.right.value == 0) return true;
// -1 < array.indexOf(string) => !!~array.indexOf(string) // -1 < array.indexOf(string) => !!~array.indexOf(string)
case "==": case "==":
case "!=": case "!=":
// -1 == array.indexOf(string) => !~array.indexOf(string) // -1 == array.indexOf(string) => !~array.indexOf(string)
// -1 != array.indexOf(string) => !!~array.indexOf(string) // -1 != array.indexOf(string) => !!~array.indexOf(string)
if (!indexRight) return false; if (!indexRight) return false;
return self.left instanceof AST_Number && self.left.getValue() == -1 return self.left instanceof AST_Number && self.left.value == -1
|| self.left instanceof AST_UnaryPrefix && self.left.operator == "-" || self.left instanceof AST_UnaryPrefix && self.left.operator == "-"
&& self.left.expression instanceof AST_Number && self.left.expression.getValue() == 1; && self.left.expression instanceof AST_Number && self.left.expression.value == 1;
} }
} }
function may_be_minus_zero(node) {
var ev = node.evaluate(compressor);
if (ev instanceof AST_Node) {
var op = ev.operator;
if (!op) return true;
if (ev instanceof AST_Assign) {
if (op == "=") return may_be_minus_zero(ev.right);
op = op.slice(0, -1);
}
if (minus_zero_op[op]) return true;
if (ev instanceof AST_UnaryPrefix && op == "+") return true;
} else if (ev == 0 && 1 / ev < 0) return true;
}
}); });
function recursive_ref(compressor, def) { function recursive_ref(compressor, def) {
@@ -6597,7 +6802,8 @@ merge(Compressor.prototype, {
var def = self.definition(); var def = self.definition();
var fixed = self.fixed_value(); var fixed = self.fixed_value();
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) {
if (fixed instanceof AST_Lambda) {
if (def.scope !== self.scope if (def.scope !== self.scope
&& (!compressor.option("reduce_funcs") || def.escaped.depth == 1 || fixed.inlined)) { && (!compressor.option("reduce_funcs") || def.escaped.depth == 1 || fixed.inlined)) {
single_use = false; single_use = false;
@@ -6613,8 +6819,11 @@ merge(Compressor.prototype, {
} }
} }
if (single_use) fixed.parent_scope = self.scope; if (single_use) fixed.parent_scope = self.scope;
} else if (!fixed || !fixed.is_constant_expression()) {
single_use = false;
} }
if (single_use && fixed) { }
if (single_use) {
def.single_use = false; def.single_use = false;
fixed._squeezed = true; fixed._squeezed = true;
fixed.single_use = true; fixed.single_use = true;
@@ -6867,7 +7076,7 @@ merge(Compressor.prototype, {
if (self.right.left instanceof AST_SymbolRef if (self.right.left instanceof AST_SymbolRef
&& self.right.left.name == self.left.name && self.right.left.name == self.left.name
&& ASSIGN_OPS[self.right.operator]) { && ASSIGN_OPS[self.right.operator]) {
// x = x - 2 ---> x -= 2 // x = x - 2 => x -= 2
self.operator = self.right.operator + "="; self.operator = self.right.operator + "=";
self.right = self.right.right; self.right = self.right.right;
} }
@@ -6875,7 +7084,7 @@ merge(Compressor.prototype, {
&& self.right.right.name == self.left.name && self.right.right.name == self.left.name
&& ASSIGN_OPS_COMMUTATIVE[self.right.operator] && ASSIGN_OPS_COMMUTATIVE[self.right.operator]
&& !self.right.left.has_side_effects(compressor)) { && !self.right.left.has_side_effects(compressor)) {
// x = 2 & x ---> x &= 2 // x = 2 & x => x &= 2
self.operator = self.right.operator + "="; self.operator = self.right.operator + "=";
self.right = self.right.left; self.right = self.right.left;
} }
@@ -6883,7 +7092,7 @@ merge(Compressor.prototype, {
if ((self.operator == "-=" || self.operator == "+=" if ((self.operator == "-=" || self.operator == "+="
&& (self.left.is_boolean(compressor) || self.left.is_number(compressor))) && (self.left.is_boolean(compressor) || self.left.is_number(compressor)))
&& self.right instanceof AST_Number && self.right instanceof AST_Number
&& self.right.getValue() === 1) { && self.right.value == 1) {
var op = self.operator.slice(0, -1); var op = self.operator.slice(0, -1);
return make_node(AST_UnaryPrefix, self, { return make_node(AST_UnaryPrefix, self, {
operator: op + op, operator: op + op,
@@ -6944,7 +7153,7 @@ merge(Compressor.prototype, {
var condition = self.condition; var condition = self.condition;
var consequent = self.consequent; var consequent = self.consequent;
var alternative = self.alternative; var alternative = self.alternative;
// x?x:y --> x||y // x ? x : y => x || y
if (condition instanceof AST_SymbolRef if (condition instanceof AST_SymbolRef
&& consequent instanceof AST_SymbolRef && consequent instanceof AST_SymbolRef
&& condition.definition() === consequent.definition()) { && condition.definition() === consequent.definition()) {
@@ -6981,16 +7190,32 @@ merge(Compressor.prototype, {
}); });
} }
} }
// x ? y(a) : y(b) --> y(x ? a : b) // x ? y : y => x, y
var arg_index; if (consequent.equivalent_to(alternative)) return make_sequence(self, [
condition,
consequent
]).optimize(compressor);
if (consequent instanceof AST_Call if (consequent instanceof AST_Call
&& alternative.TYPE === consequent.TYPE && alternative.TYPE === consequent.TYPE
&& consequent.args.length > 0 && consequent.args.length == alternative.args.length) {
&& consequent.args.length == alternative.args.length var arg_index = arg_diff();
// x ? y(a) : z(a) => (x ? y : z)(a)
if (arg_index == -1
&& !(consequent.expression instanceof AST_PropAccess)
&& !(alternative.expression instanceof AST_PropAccess)) {
var node = consequent.clone();
node.expression = make_node(AST_Conditional, self, {
condition: condition,
consequent: consequent.expression,
alternative: alternative.expression
});
return node;
}
// x ? y(a) : y(b) => y(x ? a : b)
if (arg_index >= 0
&& consequent.expression.equivalent_to(alternative.expression) && consequent.expression.equivalent_to(alternative.expression)
&& !condition.has_side_effects(compressor) && !condition.has_side_effects(compressor)
&& !consequent.expression.has_side_effects(compressor) && !consequent.expression.has_side_effects(compressor)) {
&& typeof (arg_index = single_arg_diff()) == "number") {
var node = consequent.clone(); var node = consequent.clone();
node.args[arg_index] = make_node(AST_Conditional, self, { node.args[arg_index] = make_node(AST_Conditional, self, {
condition: condition, condition: condition,
@@ -6999,7 +7224,8 @@ merge(Compressor.prototype, {
}); });
return node; return node;
} }
// x?y?z:a:a --> x&&y?z:a }
// x ? (y ? a : b) : b => x && y ? a : b
if (consequent instanceof AST_Conditional if (consequent instanceof AST_Conditional
&& consequent.alternative.equivalent_to(alternative)) { && consequent.alternative.equivalent_to(alternative)) {
return make_node(AST_Conditional, self, { return make_node(AST_Conditional, self, {
@@ -7012,14 +7238,20 @@ merge(Compressor.prototype, {
alternative: alternative alternative: alternative
}); });
} }
// x ? y : y --> x, y // x ? a : (y ? a : b) => x || y ? a : b
if (consequent.equivalent_to(alternative)) { if (alternative instanceof AST_Conditional
return make_sequence(self, [ && consequent.equivalent_to(alternative.consequent)) {
condition, return make_node(AST_Conditional, self, {
consequent condition: make_node(AST_Binary, self, {
]).optimize(compressor); left: condition,
operator: "||",
right: alternative.condition
}),
consequent: consequent,
alternative: alternative.alternative
});
} }
// x ? (y, w) : (z, w) --> x ? y : z, w // x ? (y, w) : (z, w) => x ? y : z, w
if ((consequent instanceof AST_Sequence || alternative instanceof AST_Sequence) if ((consequent instanceof AST_Sequence || alternative instanceof AST_Sequence)
&& consequent.tail_node().equivalent_to(alternative.tail_node())) { && consequent.tail_node().equivalent_to(alternative.tail_node())) {
return make_sequence(self, [ return make_sequence(self, [
@@ -7031,7 +7263,7 @@ merge(Compressor.prototype, {
consequent.tail_node() consequent.tail_node()
]).optimize(compressor); ]).optimize(compressor);
} }
// x ? y || z : z --> x && y || z // x ? y || z : z => x && y || z
if (consequent instanceof AST_Binary if (consequent instanceof AST_Binary
&& consequent.operator == "||" && consequent.operator == "||"
&& consequent.right.equivalent_to(alternative)) { && consequent.right.equivalent_to(alternative)) {
@@ -7048,10 +7280,10 @@ merge(Compressor.prototype, {
var in_bool = compressor.option("booleans") && compressor.in_boolean_context(); var in_bool = compressor.option("booleans") && compressor.in_boolean_context();
if (is_true(consequent)) { if (is_true(consequent)) {
if (is_false(alternative)) { if (is_false(alternative)) {
// c ? true : false ---> !!c // c ? true : false => !!c
return booleanize(condition); return booleanize(condition);
} }
// c ? true : x ---> !!c || x // c ? true : x => !!c || x
return make_node(AST_Binary, self, { return make_node(AST_Binary, self, {
operator: "||", operator: "||",
left: booleanize(condition), left: booleanize(condition),
@@ -7060,10 +7292,10 @@ merge(Compressor.prototype, {
} }
if (is_false(consequent)) { if (is_false(consequent)) {
if (is_true(alternative)) { if (is_true(alternative)) {
// c ? false : true ---> !c // c ? false : true => !c
return booleanize(condition.negate(compressor)); return booleanize(condition.negate(compressor));
} }
// c ? false : x ---> !c && x // c ? false : x => !c && x
return make_node(AST_Binary, self, { return make_node(AST_Binary, self, {
operator: "&&", operator: "&&",
left: booleanize(condition.negate(compressor)), left: booleanize(condition.negate(compressor)),
@@ -7071,7 +7303,7 @@ merge(Compressor.prototype, {
}); });
} }
if (is_true(alternative)) { if (is_true(alternative)) {
// c ? x : true ---> !c || x // c ? x : true => !c || x
return make_node(AST_Binary, self, { return make_node(AST_Binary, self, {
operator: "||", operator: "||",
left: booleanize(condition.negate(compressor)), left: booleanize(condition.negate(compressor)),
@@ -7079,7 +7311,7 @@ merge(Compressor.prototype, {
}); });
} }
if (is_false(alternative)) { if (is_false(alternative)) {
// c ? x : false ---> !!c && x // c ? x : false => !!c && x
return make_node(AST_Binary, self, { return make_node(AST_Binary, self, {
operator: "&&", operator: "&&",
left: booleanize(condition), left: booleanize(condition),
@@ -7103,35 +7335,39 @@ merge(Compressor.prototype, {
return node instanceof AST_True return node instanceof AST_True
|| in_bool || in_bool
&& node instanceof AST_Constant && node instanceof AST_Constant
&& node.getValue() && node.value
|| (node instanceof AST_UnaryPrefix || (node instanceof AST_UnaryPrefix
&& node.operator == "!" && node.operator == "!"
&& node.expression instanceof AST_Constant && node.expression instanceof AST_Constant
&& !node.expression.getValue()); && !node.expression.value);
} }
// AST_False or !1 // AST_False or !1 or void 0
function is_false(node) { function is_false(node) {
return node instanceof AST_False return node instanceof AST_False
|| in_bool || in_bool
&& node instanceof AST_Constant && (node instanceof AST_Constant
&& !node.getValue() && !node.value
|| node instanceof AST_UnaryPrefix
&& node.operator == "void"
&& !node.expression.has_side_effects(compressor))
|| (node instanceof AST_UnaryPrefix || (node instanceof AST_UnaryPrefix
&& node.operator == "!" && node.operator == "!"
&& node.expression instanceof AST_Constant && node.expression instanceof AST_Constant
&& node.expression.getValue()); && node.expression.value);
} }
function single_arg_diff() { function arg_diff() {
var a = consequent.args; var a = consequent.args;
var b = alternative.args; var b = alternative.args;
for (var i = 0, len = a.length; i < len; i++) { for (var i = 0, len = a.length; i < len; i++) {
if (!a[i].equivalent_to(b[i])) { if (!a[i].equivalent_to(b[i])) {
for (var j = i + 1; j < len; j++) { for (var j = i + 1; j < len; j++) {
if (!a[j].equivalent_to(b[j])) return; if (!a[j].equivalent_to(b[j])) return -2;
} }
return i; return i;
} }
} }
return -1;
} }
function can_shift_lhs_of_tail(node) { function can_shift_lhs_of_tail(node) {
@@ -7225,7 +7461,7 @@ merge(Compressor.prototype, {
&& is_arguments(def = expr.definition()) && is_arguments(def = expr.definition())
&& prop instanceof AST_Number && prop instanceof AST_Number
&& (fn = expr.scope) === find_lambda()) { && (fn = expr.scope) === find_lambda()) {
var index = prop.getValue(); var index = prop.value;
if (parent instanceof AST_UnaryPrefix && parent.operator == "delete") { if (parent instanceof AST_UnaryPrefix && parent.operator == "delete") {
if (!def.deleted) def.deleted = []; if (!def.deleted) def.deleted = [];
def.deleted[index] = true; def.deleted[index] = true;
@@ -7271,7 +7507,7 @@ merge(Compressor.prototype, {
} }
if (compressor.option("properties") && compressor.option("side_effects") if (compressor.option("properties") && compressor.option("side_effects")
&& prop instanceof AST_Number && expr instanceof AST_Array) { && prop instanceof AST_Number && expr instanceof AST_Array) {
var index = prop.getValue(); var index = prop.value;
var elements = expr.elements; var elements = expr.elements;
var retValue = elements[index]; var retValue = elements[index];
if (safe_to_flatten(retValue, compressor)) { if (safe_to_flatten(retValue, compressor)) {

View File

@@ -43,8 +43,6 @@
"use strict"; "use strict";
var EXPECT_DIRECTIVE = /^$|[;{][\s\n]*$/;
function is_some_comments(comment) { function is_some_comments(comment) {
// multiline comment // multiline comment
return comment.type == "comment2" && /@preserve|@license|@cc_on/i.test(comment.value); return comment.type == "comment2" && /@preserve|@license|@cc_on/i.test(comment.value);
@@ -121,15 +119,20 @@ function OutputStream(options) {
}); });
} : function(str) { } : function(str) {
var s = ""; var s = "";
for (var i = 0; i < str.length; i++) { for (var i = 0, j = 0; i < str.length; i++) {
if (is_surrogate_pair_head(str[i]) && !is_surrogate_pair_tail(str[i + 1]) var code = str.charCodeAt(i);
|| is_surrogate_pair_tail(str[i]) && !is_surrogate_pair_head(str[i - 1])) { if (is_surrogate_pair_head(code)) {
s += "\\u" + str.charCodeAt(i).toString(16); if (is_surrogate_pair_tail(str.charCodeAt(i + 1))) {
} else { i++;
s += str[i]; continue;
} }
} else if (!is_surrogate_pair_tail(code)) {
continue;
} }
return s; s += str.slice(j, i) + "\\u" + code.toString(16);
j = i + 1;
}
return j == 0 ? str : s + str.slice(j);
}; };
function make_string(str, quote) { function make_string(str, quote) {
@@ -378,7 +381,7 @@ function OutputStream(options) {
}; };
function force_semicolon() { function force_semicolon() {
might_need_semicolon = false; if (might_need_semicolon) print(";");
print(";"); print(";");
} }
@@ -585,17 +588,7 @@ function OutputStream(options) {
force_semicolon : force_semicolon, force_semicolon : force_semicolon,
to_utf8 : to_utf8, to_utf8 : to_utf8,
print_name : function(name) { print(make_name(name)) }, print_name : function(name) { print(make_name(name)) },
print_string : function(str, quote, escape_directive) { print_string : function(str, quote) { print(encode_string(str, quote)) },
var encoded = encode_string(str, quote);
if (escape_directive === true && encoded.indexOf("\\") === -1) {
// Insert semicolons to break directive prologue
if (!EXPECT_DIRECTIVE.test(OUTPUT)) {
force_semicolon();
}
force_semicolon();
}
print(encoded);
},
next_indent : next_indent, next_indent : next_indent,
with_indent : with_indent, with_indent : with_indent,
with_block : with_block, with_block : with_block,
@@ -633,17 +626,10 @@ function OutputStream(options) {
nodetype.DEFMETHOD("_codegen", generator); nodetype.DEFMETHOD("_codegen", generator);
} }
var in_directive = false; var use_asm = false;
var active_scope = null;
var use_asm = null;
AST_Node.DEFMETHOD("print", function(stream, force_parens) { AST_Node.DEFMETHOD("print", function(stream, force_parens) {
var self = this, generator = self._codegen; var self = this, generator = self._codegen;
if (self instanceof AST_Scope) {
active_scope = self;
} else if (!use_asm && self instanceof AST_Directive && self.value == "use asm") {
use_asm = active_scope;
}
function doit() { function doit() {
stream.prepend_comments(self); stream.prepend_comments(self);
self.add_source_map(stream); self.add_source_map(stream);
@@ -657,9 +643,6 @@ function OutputStream(options) {
doit(); doit();
} }
stream.pop_node(); stream.pop_node();
if (self === use_asm) {
use_asm = null;
}
}); });
AST_Node.DEFMETHOD("_print", AST_Node.prototype.print); AST_Node.DEFMETHOD("_print", AST_Node.prototype.print);
@@ -799,7 +782,7 @@ function OutputStream(options) {
PARENS(AST_Number, function(output) { PARENS(AST_Number, function(output) {
var p = output.parent(); var p = output.parent();
if (p instanceof AST_PropAccess && p.expression === this) { if (p instanceof AST_PropAccess && p.expression === this) {
var value = this.getValue(); var value = this.value;
if (value < 0 || /^0/.test(make_num(value))) { if (value < 0 || /^0/.test(make_num(value))) {
return true; return true;
} }
@@ -828,7 +811,18 @@ function OutputStream(options) {
/* -----[ PRINTERS ]----- */ /* -----[ PRINTERS ]----- */
DEFPRINT(AST_Directive, function(self, output) { DEFPRINT(AST_Directive, function(self, output) {
output.print_string(self.value, self.quote); var quote = self.quote;
var value = self.value;
switch (output.option("quote_style")) {
case 0:
case 2:
if (value.indexOf('"') == -1) quote = '"';
break;
case 1:
if (value.indexOf("'") == -1) quote = "'";
break;
}
output.print(quote + value + quote);
output.semicolon(); output.semicolon();
}); });
DEFPRINT(AST_Debugger, function(self, output) { DEFPRINT(AST_Debugger, function(self, output) {
@@ -840,30 +834,27 @@ function OutputStream(options) {
function display_body(body, is_toplevel, output, allow_directives) { function display_body(body, is_toplevel, output, allow_directives) {
var last = body.length - 1; var last = body.length - 1;
in_directive = allow_directives; var in_directive = allow_directives;
var was_asm = use_asm;
body.forEach(function(stmt, i) { body.forEach(function(stmt, i) {
if (in_directive === true && !(stmt instanceof AST_Directive || if (in_directive) {
stmt instanceof AST_EmptyStatement || if (stmt instanceof AST_Directive) {
(stmt instanceof AST_SimpleStatement && stmt.body instanceof AST_String) if (stmt.value == "use asm") use_asm = true;
)) { } else if (!(stmt instanceof AST_EmptyStatement)) {
if (stmt instanceof AST_SimpleStatement && stmt.body instanceof AST_String) {
output.force_semicolon();
}
in_directive = false; in_directive = false;
} }
if (!(stmt instanceof AST_EmptyStatement)) { }
if (stmt instanceof AST_EmptyStatement) return;
output.indent(); output.indent();
stmt.print(output); stmt.print(output);
if (!(i == last && is_toplevel)) { if (i == last && is_toplevel) return;
output.newline(); output.newline();
if (is_toplevel) output.newline(); if (is_toplevel) output.newline();
}
}
if (in_directive === true &&
stmt instanceof AST_SimpleStatement &&
stmt.body instanceof AST_String
) {
in_directive = false;
}
}); });
in_directive = false; use_asm = was_asm;
} }
AST_StatementWithBody.DEFMETHOD("_do_print_body", function(output) { AST_StatementWithBody.DEFMETHOD("_do_print_body", function(output) {
@@ -1221,7 +1212,7 @@ function OutputStream(options) {
output.print_string(prop); output.print_string(prop);
output.print("]"); output.print("]");
} else { } else {
if (expr instanceof AST_Number && expr.getValue() >= 0) { if (expr instanceof AST_Number && expr.value >= 0) {
if (!/[xa-f.)]/i.test(output.last())) { if (!/[xa-f.)]/i.test(output.last())) {
output.print("."); output.print(".");
} }
@@ -1345,21 +1336,21 @@ function OutputStream(options) {
output.print("this"); output.print("this");
}); });
DEFPRINT(AST_Constant, function(self, output) { DEFPRINT(AST_Constant, function(self, output) {
output.print(self.getValue()); output.print(self.value);
}); });
DEFPRINT(AST_String, function(self, output) { DEFPRINT(AST_String, function(self, output) {
output.print_string(self.getValue(), self.quote, in_directive); output.print_string(self.value, self.quote);
}); });
DEFPRINT(AST_Number, function(self, output) { DEFPRINT(AST_Number, function(self, output) {
if (use_asm && self.start && self.start.raw != null) { if (use_asm && self.start && self.start.raw != null) {
output.print(self.start.raw); output.print(self.start.raw);
} else { } else {
output.print(make_num(self.getValue())); output.print(make_num(self.value));
} }
}); });
DEFPRINT(AST_RegExp, function(self, output) { DEFPRINT(AST_RegExp, function(self, output) {
var regexp = self.getValue(); var regexp = self.value;
var str = regexp.toString(); var str = regexp.toString();
if (regexp.raw_source) { if (regexp.raw_source) {
str = "/" + regexp.raw_source + str.slice(str.lastIndexOf("/")); str = "/" + regexp.raw_source + str.slice(str.lastIndexOf("/"));

View File

@@ -133,14 +133,10 @@ function is_letter(code) {
} }
function is_surrogate_pair_head(code) { function is_surrogate_pair_head(code) {
if (typeof code == "string")
code = code.charCodeAt(0);
return code >= 0xd800 && code <= 0xdbff; return code >= 0xd800 && code <= 0xdbff;
} }
function is_surrogate_pair_tail(code) { function is_surrogate_pair_tail(code) {
if (typeof code == "string")
code = code.charCodeAt(0);
return code >= 0xdc00 && code <= 0xdfff; return code >= 0xdc00 && code <= 0xdfff;
} }
@@ -790,9 +786,10 @@ function parse($TEXT, options) {
var dir = S.in_directives; var dir = S.in_directives;
var body = expression(true); var body = expression(true);
if (dir) { if (dir) {
var token = body.start; if (body instanceof AST_String) {
if (body instanceof AST_String && token.raw.indexOf("\\") == -1) { var value = body.start.raw.slice(1, -1);
S.input.add_directive(token.value); S.input.add_directive(value);
body.value = value;
} else { } else {
S.in_directives = dir = false; S.in_directives = dir = false;
} }

View File

@@ -428,6 +428,11 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options) {
if (options.cache && node instanceof AST_Toplevel) { if (options.cache && node instanceof AST_Toplevel) {
node.globals.each(mangle); node.globals.each(mangle);
} }
if (node instanceof AST_Defun && tw.has_directive("use asm")) {
var sym = new AST_SymbolRef(node.name);
sym.scope = node;
sym.reference(options);
}
node.variables.each(function(def) { node.variables.each(function(def) {
if (!defer_redef(def)) mangle(def); if (!defer_redef(def)) mangle(def);
}); });

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.7.2", "version": "3.7.5",
"engines": { "engines": {
"node": ">=0.8.0" "node": ">=0.8.0"
}, },

View File

@@ -166,3 +166,69 @@ asm_nested_functions: {
} }
expect_exact: '0;function a(){"use asm";0.0}0;function b(){0;function c(){"use asm";0.0}0;function d(){0}0}0;' expect_exact: '0;function a(){"use asm";0.0}0;function b(){0;function c(){"use asm";0.0}0;function d(){0}0}0;'
} }
issue_3636_1: {
mangle = {}
input: {
function n(stdlib, foreign, buffer) {
"use asm";
function add(x, y) {
x = x | 0;
y = y | 0;
return x + y | 0;
}
return {
add: add
};
}
console.log(new n().add("foo", 42));
}
expect: {
function n(o, e, u) {
"use asm";
function d(n, o) {
n = n | 0;
o = o | 0;
return n + o | 0;
}
return {
add: d
};
}
console.log(new n().add("foo", 42));
}
expect_stdout: "42"
}
issue_3636_2: {
mangle = {}
input: {
var n = function(stdlib, foreign, buffer) {
"use asm";
function add(x, y) {
x = x | 0;
y = y | 0;
return x + y | 0;
}
return {
add: add
};
};
console.log(new n().add("foo", 42));
}
expect: {
var n = function(n, o, e) {
"use asm";
function r(n, o) {
n = n | 0;
o = o | 0;
return n + o | 0;
}
return {
add: r
};
};
console.log(new n().add("foo", 42));
}
expect_stdout: "42"
}

View File

@@ -86,3 +86,48 @@ issue_3465_3: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
issue_2737_2: {
options = {
booleans: true,
inline: true,
reduce_vars: true,
unused: true,
}
input: {
(function(bar) {
for (;bar();) break;
})(function qux() {
return console.log("PASS"), qux;
});
}
expect: {
(function(bar) {
for (;bar();) break;
})(function() {
return console.log("PASS"), 1;
});
}
expect_stdout: "PASS"
}
issue_3658: {
options = {
booleans: true,
evaluate: true,
reduce_vars: true,
}
input: {
console.log(function f() {
console || f();
return "PASS";
}());
}
expect: {
console.log(function f() {
console || f();
return "PASS";
}());
}
expect_stdout: "PASS"
}

View File

@@ -5863,8 +5863,8 @@ issue_2974: {
var c = 0; var c = 0;
(function(b) { (function(b) {
var a = 2; var a = 2;
for (; b.null = -4, c++, b.null && --a > 0;); for (;c++, (!0).null && --a > 0;);
})(!0), })(),
console.log(c); console.log(c);
} }
expect_stdout: "1" expect_stdout: "1"
@@ -7399,3 +7399,95 @@ issue_3628_2: {
} }
expect_stdout: "foo bar" expect_stdout: "foo bar"
} }
issue_3641: {
options = {
collapse_vars: true,
}
input: {
var a, b;
try {
a = "foo";
b = (a += (A.p = 0, "bar")) % 0;
} catch (e) {}
console.log(a, b);
}
expect: {
var a, b;
try {
a = "foo";
b = (a += (A.p = 0, "bar")) % 0;
} catch (e) {}
console.log(a, b);
}
expect_stdout: "foo undefined"
}
issue_3651: {
options = {
collapse_vars: true,
toplevel: true,
}
input: {
var a, b = "PASS";
try {
a = function() {
try {
var c = 1;
while (0 < --c);
} catch (e) {} finally {
throw 42;
}
}();
b = "FAIL";
a.p;
} catch (e) {
console.log(b);
}
}
expect: {
var a, b = "PASS";
try {
a = function() {
try {
var c = 1;
while (0 < --c);
} catch (e) {} finally {
throw 42;
}
}();
b = "FAIL";
a.p;
} catch (e) {
console.log(b);
}
}
expect_stdout: "PASS"
}
issue_3671: {
options = {
collapse_vars: true,
}
input: {
var a = 0;
try {
a++;
A += 0;
a = 1 + a;
} catch (e) {
console.log(a);
}
}
expect: {
var a = 0;
try {
a++;
A += 0;
a = 1 + a;
} catch (e) {
console.log(a);
}
}
expect_stdout: "1"
}

View File

@@ -33,10 +33,10 @@ unsafe_comps: {
} }
expect: { expect: {
var obj1, obj2; var obj1, obj2;
obj2 < obj1 ? g1() : f1(); (obj2 < obj1 ? g1 : f1)();
obj1 < obj2 ? f2() : g2(); (obj1 < obj2 ? f2 : g2)();
obj1 < obj2 ? g3() : f3(); (obj1 < obj2 ? g3 : f3)();
obj2 < obj1 ? f4() : g4(); (obj2 < obj1 ? f4 : g4)();
} }
} }

View File

@@ -41,7 +41,7 @@ ifs_2: {
} }
expect: { expect: {
foo ? x() : bar ? y() : baz && z(); foo ? x() : bar ? y() : baz && z();
foo ? x() : bar ? y() : baz ? z() : t(); (foo ? x : bar ? y : baz ? z : t)();
} }
} }
@@ -289,7 +289,7 @@ cond_5: {
} }
} }
expect: { expect: {
some_condition() && some_other_condition() ? do_something() : alternate(); (some_condition() && some_other_condition() ? do_something : alternate)();
some_condition() && some_other_condition() && do_something(); some_condition() && some_other_condition() && do_something();
} }
} }
@@ -663,6 +663,69 @@ cond_9: {
} }
} }
cond_10: {
options = {
conditionals: true,
if_return: true,
}
input: {
function f(a) {
if (1 == a) return "foo";
if (2 == a) return "foo";
if (3 == a) return "foo";
if (4 == a) return 42;
if (5 == a) return "foo";
if (6 == a) return "foo";
return "bar";
}
console.log(f(1), f(2), f(3), f(4), f(5), f(6), f(7));
}
expect: {
function f(a) {
return 1 == a || 2 == a || 3 == a ? "foo" : 4 == a ? 42 : 5 == a || 6 == a ? "foo" : "bar";
}
console.log(f(1), f(2), f(3), f(4), f(5), f(6), f(7));
}
expect_stdout: "foo foo foo 42 foo foo bar"
}
cond_11: {
options = {
conditionals: true,
}
input: {
var o = {
p: "foo",
q: function() {
return this.p;
}
};
function f() {
return "bar";
}
function g(a) {
return a ? f() : o.q();
}
console.log(g(0), g(1));
}
expect: {
var o = {
p: "foo",
q: function() {
return this.p;
}
};
function f() {
return "bar";
}
function g(a) {
return a ? f() : o.q();
}
console.log(g(0), g(1));
}
expect_stdout: "foo bar"
}
ternary_boolean_consequent: { ternary_boolean_consequent: {
options = { options = {
booleans: true, booleans: true,
@@ -1515,3 +1578,34 @@ issue_3576: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
issue_3668: {
options = {
conditionals: true,
if_return: true,
}
input: {
function f() {
try {
var undefined = typeof f;
if (!f) return undefined;
return;
} catch (e) {
return "FAIL";
}
}
console.log(f());
}
expect: {
function f() {
try {
var undefined = typeof f;
return f ? void 0 : undefined;
} catch (e) {
return "FAIL";
}
}
console.log(f());
}
expect_stdout: "undefined"
}

View File

@@ -141,207 +141,6 @@ try_catch_finally: {
] ]
} }
accessor: {
options = {
side_effects: true,
}
input: {
({
get a() {},
set a(v){
this.b = 2;
},
b: 1
});
}
expect: {}
}
issue_2233_1: {
options = {
pure_getters: "strict",
side_effects: true,
unsafe: true,
}
input: {
Array.isArray;
Boolean;
console.log;
Date;
decodeURI;
decodeURIComponent;
encodeURI;
encodeURIComponent;
Error.name;
escape;
eval;
EvalError;
Function.length;
isFinite;
isNaN;
JSON;
Math.random;
Number.isNaN;
parseFloat;
parseInt;
RegExp;
Object.defineProperty;
String.fromCharCode;
RangeError;
ReferenceError;
SyntaxError;
TypeError;
unescape;
URIError;
}
expect: {}
expect_stdout: true
}
global_timeout_and_interval_symbols: {
options = {
pure_getters: "strict",
side_effects: true,
unsafe: true,
}
input: {
// These global symbols do not exist in the test sandbox
// and must be tested separately.
clearInterval;
clearTimeout;
setInterval;
setTimeout;
}
expect: {}
}
issue_2233_2: {
options = {
pure_getters: "strict",
reduce_funcs: true,
reduce_vars: true,
side_effects: true,
unsafe: true,
unused: true,
}
input: {
var RegExp;
Array.isArray;
RegExp;
UndeclaredGlobal;
function foo() {
var Number;
AnotherUndeclaredGlobal;
Math.sin;
Number.isNaN;
}
}
expect: {
var RegExp;
UndeclaredGlobal;
function foo() {
var Number;
AnotherUndeclaredGlobal;
Number.isNaN;
}
}
}
issue_2233_3: {
options = {
pure_getters: "strict",
reduce_funcs: true,
reduce_vars: true,
side_effects: true,
toplevel: true,
unsafe: true,
unused: true,
}
input: {
var RegExp;
Array.isArray;
RegExp;
UndeclaredGlobal;
function foo() {
var Number;
AnotherUndeclaredGlobal;
Math.sin;
Number.isNaN;
}
}
expect: {
UndeclaredGlobal;
}
}
global_fns: {
options = {
side_effects: true,
unsafe: true,
}
input: {
Boolean(1, 2);
decodeURI(1, 2);
decodeURIComponent(1, 2);
Date(1, 2);
encodeURI(1, 2);
encodeURIComponent(1, 2);
Error(1, 2);
escape(1, 2);
EvalError(1, 2);
isFinite(1, 2);
isNaN(1, 2);
Number(1, 2);
Object(1, 2);
parseFloat(1, 2);
parseInt(1, 2);
RangeError(1, 2);
ReferenceError(1, 2);
String(1, 2);
SyntaxError(1, 2);
TypeError(1, 2);
unescape(1, 2);
URIError(1, 2);
try {
Function(1, 2);
} catch (e) {
console.log(e.name);
}
try {
RegExp(1, 2);
} catch (e) {
console.log(e.name);
}
try {
Array(NaN);
} catch (e) {
console.log(e.name);
}
}
expect: {
try {
Function(1, 2);
} catch (e) {
console.log(e.name);
}
try {
RegExp(1, 2);
} catch (e) {
console.log(e.name);
}
try {
Array(NaN);
} catch (e) {
console.log(e.name);
}
}
expect_stdout: [
"SyntaxError",
"SyntaxError",
"RangeError",
]
}
collapse_vars_assignment: { collapse_vars_assignment: {
options = { options = {
collapse_vars: true, collapse_vars: true,
@@ -863,23 +662,6 @@ issue_2749: {
expect_stdout: "PASS" expect_stdout: "PASS"
} }
unsafe_builtin: {
options = {
side_effects: true,
unsafe: true,
}
input: {
(!w).constructor(x);
Math.abs(y);
[ 1, 2, z ].valueOf();
}
expect: {
w, x;
y;
z;
}
}
issue_2860_1: { issue_2860_1: {
options = { options = {
dead_code: true, dead_code: true,
@@ -941,24 +723,6 @@ 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"
}
issue_3402: { issue_3402: {
options = { options = {
dead_code: true, dead_code: true,

View File

@@ -93,3 +93,41 @@ issue_3166: {
} }
} }
} }
valid_after_invalid_1: {
input: {
console.log(typeof function() {
"use\x20strict";
"use strict";
return this;
}());
}
expect: {
console.log(typeof function() {
"use\x20strict";
"use strict";
return this;
}());
}
expect_stdout: "undefined"
}
valid_after_invalid_2: {
options = {
directives: true,
}
input: {
console.log(typeof function() {
"use\x20strict";
"use strict";
return this;
}());
}
expect: {
console.log(typeof function() {
"use strict";
return this;
}());
}
expect_stdout: "undefined"
}

View File

@@ -699,18 +699,6 @@ iife: {
} }
} }
drop_value: {
options = {
side_effects: true,
}
input: {
(1, [2, foo()], 3, {a:1, b:bar()});
}
expect: {
foo(), bar();
}
}
issue_1539: { issue_1539: {
options = { options = {
collapse_vars: true, collapse_vars: true,
@@ -2266,3 +2254,124 @@ issue_3598: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
self_assign: {
options = {
passes: 2,
side_effects: true,
unused: true,
}
input: {
function d(a) {
a = a;
}
function e(a, b) {
a = b;
b = a;
}
function f(a, b, c) {
a = b;
b = c;
c = a;
}
function g(a, b, c) {
a = a * b + c;
}
}
expect: {
function d(a) {}
function e(a, b) {}
function f(a, b, c) {}
function g(a, b, c) {}
}
}
function_argument_reference: {
options = {
keep_fargs: false,
side_effects: true,
unused: true,
}
input: {
var a = 1, b = 42;
function f(a) {
b <<= a;
}
f();
console.log(a, b);
}
expect: {
var a = 1, b = 42;
function f(a) {
b <<= a;
}
f();
console.log(a, b);
}
expect_stdout: "1 42"
}
function_parameter_ie8: {
options = {
ie8: true,
reduce_vars: true,
unused: true,
}
input: {
(function() {
var a;
function f() {
console.log("PASS");
}
f(a = 1 + a);
})();
}
expect: {
(function() {
(function f() {
console.log("PASS");
})(0);
})();
}
expect_stdout: "PASS"
}
issue_3664: {
options = {
pure_getters: "strict",
unused: true,
}
input: {
console.log(function() {
var a, b = (a = (a = [ b && console.log("FAIL") ]).p = 0, 0);
return "PASS";
}());
}
expect: {
console.log(function() {
var b = ([ b && console.log("FAIL") ].p = 0, 0);
return "PASS";
}());
}
expect_stdout: "PASS"
}
issue_3673: {
options = {
pure_getters: "strict",
side_effects: true,
toplevel: true,
unused: true,
}
input: {
var a;
(a = [ a ]).p = 42;
console.log("PASS");
}
expect: {
var a;
(a = [ a ]).p = 42;
console.log("PASS");
}
expect_stdout: "PASS"
}

View File

@@ -266,12 +266,7 @@ issue_2084: {
} }
expect: { expect: {
var c = 0; var c = 0;
!function() { 23..toString(),
var c;
c = 1 + (c = -1),
c = 1 + (c = 0),
0 !== 23..toString() && (c = 1 + c);
}(),
console.log(c); console.log(c);
} }
expect_stdout: "0" expect_stdout: "0"
@@ -1911,14 +1906,14 @@ issue_2737_2: {
} }
input: { input: {
(function(bar) { (function(bar) {
for (;bar(); ) break; for (;bar();) break;
})(function qux() { })(function qux() {
return console.log("PASS"), qux; return console.log("PASS"), qux;
}); });
} }
expect: { expect: {
(function(bar) { (function(bar) {
for (;bar(); ) break; for (;bar();) break;
})(function qux() { })(function qux() {
return console.log("PASS"), qux; return console.log("PASS"), qux;
}); });
@@ -3705,3 +3700,88 @@ pr_3595_4: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
issue_3679_1: {
options = {
collapse_vars: true,
inline: true,
pure_getters: "strict",
reduce_vars: true,
side_effects: true,
unused: true,
}
input: {
(function() {
var f = function() {};
f.g = function() {
console.log("PASS");
};
f.g();
})();
}
expect: {
console.log("PASS");
}
expect_stdout: "PASS"
}
issue_3679_2: {
options = {
collapse_vars: true,
inline: true,
passes: 2,
pure_getters: "strict",
reduce_vars: true,
side_effects: true,
unused: true,
}
input: {
(function() {
"use strict";
var f = function() {};
f.g = function() {
console.log("PASS");
};
f.g();
})();
}
expect: {
(function() {
"use strict";
console.log("PASS");
})();
}
expect_stdout: "PASS"
}
issue_3679_3: {
options = {
collapse_vars: true,
inline: true,
functions: true,
pure_getters: "strict",
reduce_vars: true,
side_effects: true,
unused: true,
}
input: {
(function() {
var f = function() {};
f.p = "PASS";
f.g = function() {
console.log(f.p);
};
f.g();
})();
}
expect: {
(function() {
function f() {};
f.p = "PASS";
(f.g = function() {
console.log(f.p);
})();
})();
}
expect_stdout: "PASS"
}

View File

@@ -664,7 +664,7 @@ issue_2519: {
} }
expect: { expect: {
function testFunc() { function testFunc() {
return 1 * ((6 + 5) / 2); return +((6 + 5) / 2);
} }
console.log(testFunc()); console.log(testFunc());
} }

View File

@@ -30,7 +30,7 @@ non_hoisted_function_after_return: {
} }
expect: { expect: {
function foo(x) { function foo(x) {
return x ? bar() : baz(); return (x ? bar : baz)();
function bar() { return 7 } function bar() { return 7 }
function baz() { return 8 } function baz() { return 8 }
} }
@@ -181,7 +181,7 @@ non_hoisted_function_after_return_strict: {
expect: { expect: {
"use strict"; "use strict";
function foo(x) { function foo(x) {
return x ? bar() : baz(); return (x ? bar : baz)();
function bar() { return 7 } function bar() { return 7 }
function baz() { return 8 } function baz() { return 8 }
} }

View File

@@ -21,7 +21,7 @@ cond_5: {
} }
} }
expect: { expect: {
some_condition() && some_other_condition() ? do_something() : alternate(); (some_condition() && some_other_condition() ? do_something : alternate)();
if (some_condition() && some_other_condition()) do_something(); if (some_condition() && some_other_condition()) do_something();
} }
} }

View File

@@ -6,7 +6,7 @@ while_becomes_for: {
while (foo()) bar(); while (foo()) bar();
} }
expect: { expect: {
for (; foo(); ) bar(); for (;foo();) bar();
} }
} }
@@ -19,7 +19,7 @@ drop_if_break_1: {
if (foo()) break; if (foo()) break;
} }
expect: { expect: {
for (; !foo();); for (;!foo(););
} }
} }
@@ -32,7 +32,7 @@ drop_if_break_2: {
if (foo()) break; if (foo()) break;
} }
expect: { expect: {
for (; bar() && !foo();); for (;bar() && !foo(););
} }
} }
@@ -70,7 +70,7 @@ drop_if_break_4: {
} }
} }
expect: { expect: {
for (; bar() && (x(), y(), !foo());) z(), k(); for (;bar() && (x(), y(), !foo());) z(), k();
} }
} }
@@ -82,7 +82,7 @@ drop_if_else_break_1: {
for (;;) if (foo()) bar(); else break; for (;;) if (foo()) bar(); else break;
} }
expect: { expect: {
for (; foo(); ) bar(); for (;foo();) bar();
} }
} }
@@ -97,7 +97,7 @@ drop_if_else_break_2: {
} }
} }
expect: { expect: {
for (; bar() && foo();) baz(); for (;bar() && foo();) baz();
} }
} }
@@ -114,7 +114,7 @@ drop_if_else_break_3: {
} }
} }
expect: { expect: {
for (; bar() && foo();) { for (;bar() && foo();) {
baz(); baz();
stuff1(); stuff1();
stuff2(); stuff2();
@@ -138,7 +138,7 @@ drop_if_else_break_4: {
} }
} }
expect: { expect: {
for (; bar() && (x(), y(), foo());) baz(), z(), k(); for (;bar() && (x(), y(), foo());) baz(), z(), k();
} }
} }
@@ -523,13 +523,13 @@ issue_2740_1: {
loops: true, loops: true,
} }
input: { input: {
for (; ; ) break; for (;;) break;
for (a(); ; ) break; for (a();;) break;
for (; b(); ) break; for (;b();) break;
for (c(); d(); ) break; for (c(); d();) break;
for (; ; e()) break; for (;;e()) break;
for (f(); ; g()) break; for (f();; g()) break;
for (; h(); i()) break; for (;h(); i()) break;
for (j(); k(); l()) break; for (j(); k(); l()) break;
} }
expect: { expect: {
@@ -574,9 +574,11 @@ issue_2740_3: {
console.log(x, y); console.log(x, y);
} }
expect: { expect: {
L1: for (var x = 0; x < 3; x++) L1: for (var x = 0; x < 3; x++) {
for (var y = 0; y < 2; y++) var y = 0;
if (y < 2)
break L1; break L1;
}
console.log(x, y); console.log(x, y);
} }
expect_stdout: "0 0" expect_stdout: "0 0"
@@ -668,7 +670,7 @@ issue_3371: {
function a() { function a() {
console.log("PASS"); console.log("PASS");
} }
for (; a(); ); for (;a(););
})(); })();
} }
expect_stdout: "PASS" expect_stdout: "PASS"
@@ -753,3 +755,175 @@ empty_for_in_side_effects: {
"WARN: Side effects in object of for-in loop [test/compress/loops.js:1,17]", "WARN: Side effects in object of for-in loop [test/compress/loops.js:1,17]",
] ]
} }
issue_3631_1: {
options = {
dead_code: true,
evaluate: true,
loops: true,
reduce_vars: true,
toplevel: true,
}
input: {
var c = 0;
L: do {
for (;;) continue L;
var b = 1;
} while (b && c++);
console.log(c);
}
expect: {
var c = 0;
do {
var b;
} while (b && c++);
console.log(c);
}
expect_stdout: "0"
}
issue_3631_2: {
options = {
dead_code: true,
evaluate: true,
loops: true,
reduce_vars: true,
toplevel: true,
}
input: {
L: for (var a = 1; a--; console.log(b)) {
for (;;) continue L;
var b = "FAIL";
}
}
expect: {
for (var a = 1; a--; console.log(b))
var b;
}
expect_stdout: "undefined"
}
loop_if_break: {
options = {
dead_code: true,
loops: true,
}
input: {
function f(a, b) {
try {
while (a) {
if (b) {
break;
var c = 42;
console.log(c);
} else {
var d = false;
throw d;
}
}
} catch (e) {
console.log("E:", e);
}
console.log(a, b, c, d);
}
f(0, 0);
f(0, 1);
f(1, 0);
f(1, 1);
}
expect: {
function f(a, b) {
try {
for (;a && !b;) {
var d = false;
throw d;
var c;
}
} catch (e) {
console.log("E:", e);
}
console.log(a, b, c, d);
}
f(0, 0);
f(0, 1);
f(1, 0);
f(1, 1);
}
expect_stdout: [
"0 0 undefined undefined",
"0 1 undefined undefined",
"E: false",
"1 0 undefined false",
"1 1 undefined undefined",
]
}
loop_return: {
options = {
dead_code: true,
loops: true,
}
input: {
function f(a) {
while (a) return 42;
return "foo";
}
console.log(f(0), f(1));
}
expect: {
function f(a) {
if (a) return 42;
return "foo";
}
console.log(f(0), f(1));
}
expect_stdout: "foo 42"
}
issue_3634_1: {
options = {
loops: true,
}
input: {
var b = 0;
L: while (++b < 2)
while (1)
if (b) break L;
console.log(b);
}
expect: {
var b = 0;
L: for (;++b < 2;)
for (;1;)
if (b) break L;
console.log(b);
}
expect_stdout: "1"
}
issue_3634_2: {
options = {
loops: true,
}
input: {
var b = 0;
L: while (++b < 2)
while (1)
if (!b)
continue L;
else
break L;
console.log(b);
}
expect: {
var b = 0;
L: for (;++b < 2;)
for (;1;)
if (!b)
continue L;
else
break L;
console.log(b);
}
expect_stdout: "1"
}

View File

@@ -91,7 +91,7 @@ evaluate_1: {
expect: { expect: {
console.log( console.log(
x + 1 + 2, x + 1 + 2,
1 * x * 2, 2 * x,
+x + 1 + 2, +x + 1 + 2,
1 + x + 2 + 3, 1 + x + 2 + 3,
3 | x, 3 | x,
@@ -173,7 +173,7 @@ evaluate_2: {
var x = "42", y = null; var x = "42", y = null;
[ [
x + 1 + 2, x + 1 + 2,
1 * x * 2, 2 * x,
+x + 1 + 2, +x + 1 + 2,
1 + x + 2 + 3, 1 + x + 2 + 3,
3 | x, 3 | x,
@@ -979,3 +979,191 @@ unsafe_math_swap_constant: {
} }
expect_stdout: "6 6 7 6 6 8 9 10" expect_stdout: "6 6 7 6 6 8 9 10"
} }
identity_1: {
options = {
evaluate: true,
}
input: {
0 + a;
a + 0;
0 - a;
a - 0;
1 * a;
a * 1;
1 / a;
a / 1;
}
expect: {
0 + a;
a + 0;
0 - a;
+a;
+a;
+a;
1 / a;
+a;
}
}
identity_2: {
options = {
evaluate: true,
}
input: {
0 + !a;
!a + 0;
0 - !a;
!a - 0;
1 * !a;
!a * 1;
1 / !a;
!a / 1;
}
expect: {
+!a;
+!a;
0 - !a;
+!a;
+!a;
+!a;
1 / !a;
+!a;
}
}
identity_3: {
options = {
evaluate: true,
}
input: {
0 + --a;
--a + 0;
0 - --a;
--a - 0;
1 * --a;
--a * 1;
1 / --a;
--a / 1;
}
expect: {
--a;
--a;
0 - --a;
--a;
--a;
--a;
1 / --a;
--a;
}
}
issue_3653: {
options = {
evaluate: true,
}
input: {
console.log(0 - (console && 0));
console.log(0 + (0 - (console && 0)));
console.log(0 - (0 - (console && 0)));
console.log(1 * (0 - (console && 0)));
console.log(1 / (0 - (console && 0)));
console.log((0 - (console && 0)) + 0);
console.log((0 - (console && 0)) - 0);
console.log((0 - (console && 0)) * 1);
console.log((0 - (console && 0)) / 1);
}
expect: {
console.log(0 - (console && 0));
console.log(0 - (console && 0) + 0);
console.log(0 - (0 - (console && 0)));
console.log(0 - (console && 0));
console.log(1 / (0 - (console && 0)));
console.log(0 - (console && 0) + 0);
console.log(0 - (console && 0));
console.log(0 - (console && 0));
console.log(0 - (console && 0));
}
expect_stdout: [
"0",
"0",
"0",
"0",
"Infinity",
"0",
"0",
"0",
"0",
]
}
issue_3655: {
options = {
evaluate: true,
}
input: {
console.log(0 + 0 * -[].length);
console.log(0 + (0 + 0 * -[].length));
console.log(0 - (0 + 0 * -[].length));
console.log(1 * (0 + 0 * -[].length));
console.log(1 / (0 + 0 * -[].length));
console.log((0 + 0 * -[].length) + 0);
console.log((0 + 0 * -[].length) - 0);
console.log((0 + 0 * -[].length) * 1);
console.log((0 + 0 * -[].length) / 1);
}
expect: {
console.log(0 + 0 * -[].length);
console.log(0 + 0 * -[].length);
console.log(0 - (0 + 0 * -[].length));
console.log(0 + 0 * -[].length);
console.log(1 / (0 + 0 * -[].length));
console.log(0 + 0 * -[].length);
console.log(0 + 0 * -[].length);
console.log(0 + 0 * -[].length);
console.log(0 + 0 * -[].length);
}
expect_stdout: [
"0",
"0",
"0",
"0",
"Infinity",
"0",
"0",
"0",
"0",
]
}
issue_3676_1: {
options = {
evaluate: true,
unsafe_math: true,
}
input: {
var a = [];
console.log(false - (a - (a[1] = 42)));
}
expect: {
var a = [];
console.log(false - (a - (a[1] = 42)));
}
expect_stdout: "NaN"
}
issue_3676_2: {
options = {
evaluate: true,
unsafe_math: true,
}
input: {
var a;
console.log(false - ((a = []) - (a[1] = 42)));
}
expect: {
var a;
console.log(false - ((a = []) - (a[1] = 42)));
}
expect_stdout: "NaN"
}

View File

@@ -6801,3 +6801,80 @@ issue_3622: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
issue_3631_1: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
}
input: {
var c = 0;
L: do {
for (;;) continue L;
var b = 1;
} while (b && c++);
console.log(c);
}
expect: {
var c = 0;
L: do {
for (;;) continue L;
var b = 1;
} while (b && c++);
console.log(c);
}
expect_stdout: "0"
}
issue_3631_2: {
options = {
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
L: for (var a = 1; a--; console.log(b)) {
for (;;) continue L;
var b = "FAIL";
}
}
expect: {
L: for (var a = 1; a--; console.log(b)) {
for (;;) continue L;
var b = "FAIL";
}
}
expect_stdout: "undefined"
}
issue_3666: {
options = {
collapse_vars: true,
passes: 2,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
try {
var a = "FAIL";
} finally {
for (;!a;)
var c = a++;
var a = "PASS", b = c = "PASS";
}
console.log(a, b);
}
expect: {
try {
var a = "FAIL";
} finally {
for (;!a;)
a++;
var b = a = "PASS";
}
console.log(a, b);
}
expect_stdout: "PASS PASS"
}

View File

@@ -0,0 +1,277 @@
accessor: {
options = {
side_effects: true,
}
input: {
({
get a() {},
set a(v){
this.b = 2;
},
b: 1
});
}
expect: {}
}
issue_2233_1: {
options = {
pure_getters: "strict",
side_effects: true,
unsafe: true,
}
input: {
Array.isArray;
Boolean;
console.log;
Date;
decodeURI;
decodeURIComponent;
encodeURI;
encodeURIComponent;
Error.name;
escape;
eval;
EvalError;
Function.length;
isFinite;
isNaN;
JSON;
Math.random;
Number.isNaN;
parseFloat;
parseInt;
RegExp;
Object.defineProperty;
String.fromCharCode;
RangeError;
ReferenceError;
SyntaxError;
TypeError;
unescape;
URIError;
}
expect: {}
expect_stdout: true
}
global_timeout_and_interval_symbols: {
options = {
pure_getters: "strict",
side_effects: true,
unsafe: true,
}
input: {
// These global symbols do not exist in the test sandbox
// and must be tested separately.
clearInterval;
clearTimeout;
setInterval;
setTimeout;
}
expect: {}
}
issue_2233_2: {
options = {
pure_getters: "strict",
reduce_funcs: true,
reduce_vars: true,
side_effects: true,
unsafe: true,
unused: true,
}
input: {
var RegExp;
Array.isArray;
RegExp;
UndeclaredGlobal;
function foo() {
var Number;
AnotherUndeclaredGlobal;
Math.sin;
Number.isNaN;
}
}
expect: {
var RegExp;
UndeclaredGlobal;
function foo() {
var Number;
AnotherUndeclaredGlobal;
Number.isNaN;
}
}
}
issue_2233_3: {
options = {
pure_getters: "strict",
reduce_funcs: true,
reduce_vars: true,
side_effects: true,
toplevel: true,
unsafe: true,
unused: true,
}
input: {
var RegExp;
Array.isArray;
RegExp;
UndeclaredGlobal;
function foo() {
var Number;
AnotherUndeclaredGlobal;
Math.sin;
Number.isNaN;
}
}
expect: {
UndeclaredGlobal;
}
}
global_fns: {
options = {
side_effects: true,
unsafe: true,
}
input: {
Boolean(1, 2);
decodeURI(1, 2);
decodeURIComponent(1, 2);
Date(1, 2);
encodeURI(1, 2);
encodeURIComponent(1, 2);
Error(1, 2);
escape(1, 2);
EvalError(1, 2);
isFinite(1, 2);
isNaN(1, 2);
Number(1, 2);
Object(1, 2);
parseFloat(1, 2);
parseInt(1, 2);
RangeError(1, 2);
ReferenceError(1, 2);
String(1, 2);
SyntaxError(1, 2);
TypeError(1, 2);
unescape(1, 2);
URIError(1, 2);
try {
Function(1, 2);
} catch (e) {
console.log(e.name);
}
try {
RegExp(1, 2);
} catch (e) {
console.log(e.name);
}
try {
Array(NaN);
} catch (e) {
console.log(e.name);
}
}
expect: {
try {
Function(1, 2);
} catch (e) {
console.log(e.name);
}
try {
RegExp(1, 2);
} catch (e) {
console.log(e.name);
}
try {
Array(NaN);
} catch (e) {
console.log(e.name);
}
}
expect_stdout: [
"SyntaxError",
"SyntaxError",
"RangeError",
]
}
unsafe_builtin_1: {
options = {
side_effects: true,
unsafe: true,
}
input: {
(!w).constructor(x);
Math.abs(y);
[ 1, 2, z ].valueOf();
}
expect: {
w, x;
y;
z;
}
}
unsafe_builtin_2: {
options = {
side_effects: true,
unsafe: true,
}
input: {
var o = {};
constructor.call(o, 42);
__defineGetter__.call(o, "foo", function() {
return o.p;
});
__defineSetter__.call(o, void 0, function(a) {
o.p = a;
});
console.log(typeof o, o.undefined = "PASS", o.foo);
}
expect: {
var o = {};
constructor.call(o, 42);
__defineGetter__.call(o, "foo", function() {
return o.p;
});
__defineSetter__.call(o, void 0, function(a) {
o.p = a;
});
console.log(typeof o, o.undefined = "PASS", o.foo);
}
expect_stdout: "object PASS 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"
}
drop_value: {
options = {
side_effects: true,
}
input: {
(1, [2, foo()], 3, {a:1, b:bar()});
}
expect: {
foo(), bar();
}
}

View File

@@ -103,6 +103,7 @@ if_return: {
booleans: true, booleans: true,
conditionals: true, conditionals: true,
if_return: true, if_return: true,
passes: 2,
sequences: true, sequences: true,
side_effects: true, side_effects: true,
} }

View File

@@ -16,6 +16,81 @@ unicode_parse_variables: {
} }
} }
unicode_escaped_identifier: {
input: {
var \u0061 = "\ud800\udc00";
console.log(a);
}
expect_exact: 'var a="\ud800\udc00";console.log(a);'
expect_stdout: "\ud800\udc00"
}
unicode_identifier_ascii_only: {
beautify = {
ascii_only: true,
}
input: {
var \u0061 = "testing \udbc4\udd11";
var bar = "h\u0065llo";
console.log(a, \u0062\u0061r);
}
expect_exact: 'var a="testing \\udbc4\\udd11";var bar="hello";console.log(a,bar);'
expect_stdout: "testing \udbc4\udd11 hello"
}
unicode_string_literals: {
beautify = {
ascii_only: true,
}
input: {
var a = "6 length unicode character: \udbc4\udd11";
console.log(\u0061);
}
expect_exact: 'var a="6 length unicode character: \\udbc4\\udd11";console.log(a);'
expect_stdout: "6 length unicode character: \udbc4\udd11"
}
check_escape_style: {
beautify = {
ascii_only: true,
}
input: {
var a = "\x01";
var \ua0081 = "\x10"; // \u0081 only in ID_Continue
var \u0100 = "\u0100";
var \u1000 = "\u1000";
var \u1000 = "\ud800\udc00";
var \u3f80 = "\udbc0\udc00";
console.log(\u0061, \ua0081, \u0100, \u1000, \u3f80);
}
expect_exact: 'var a="\\x01";var \\ua0081="\\x10";var \\u0100="\\u0100";var \\u1000="\\u1000";var \\u1000="\\ud800\\udc00";var \\u3f80="\\udbc0\\udc00";console.log(a,\\ua0081,\\u0100,\\u1000,\\u3f80);'
expect_stdout: "\u0001 \u0010 \u0100 \ud800\udc00 \udbc0\udc00"
}
escape_non_escaped_identifier: {
beautify = {
ascii_only: true,
}
input: {
var µþ = "µþ";
console.log(\u00b5þ);
}
expect_exact: 'var \\u00b5\\u00fe="\\xb5\\xfe";console.log(\\u00b5\\u00fe);'
expect_stdout: "µþ"
}
non_escape_2_non_escape: {
beautify = {
ascii_only: false,
}
input: {
var µþ = "µþ";
console.log(\u00b5þ);
}
expect_exact: 'var µþ="µþ";console.log(µþ);'
expect_stdout: "µþ"
}
issue_2242_1: { issue_2242_1: {
beautify = { beautify = {
ascii_only: false, ascii_only: false,
@@ -24,6 +99,7 @@ issue_2242_1: {
console.log("\ud83d", "\ude00", "\ud83d\ude00", "\ud83d@\ude00"); console.log("\ud83d", "\ude00", "\ud83d\ude00", "\ud83d@\ude00");
} }
expect_exact: 'console.log("\\ud83d","\\ude00","\ud83d\ude00","\\ud83d@\\ude00");' expect_exact: 'console.log("\\ud83d","\\ude00","\ud83d\ude00","\\ud83d@\\ude00");'
expect_stdout: "\ud83d \ude00 \ud83d\ude00 \ud83d@\ude00"
} }
issue_2242_2: { issue_2242_2: {
@@ -34,6 +110,7 @@ issue_2242_2: {
console.log("\ud83d", "\ude00", "\ud83d\ude00", "\ud83d@\ude00"); console.log("\ud83d", "\ude00", "\ud83d\ude00", "\ud83d@\ude00");
} }
expect_exact: 'console.log("\\ud83d","\\ude00","\\ud83d\\ude00","\\ud83d@\\ude00");' expect_exact: 'console.log("\\ud83d","\\ude00","\\ud83d\\ude00","\\ud83d@\\ude00");'
expect_stdout: "\ud83d \ude00 \ud83d\ude00 \ud83d@\ude00"
} }
issue_2242_3: { issue_2242_3: {
@@ -44,6 +121,7 @@ issue_2242_3: {
console.log("\ud83d" + "\ude00", "\ud83d" + "@" + "\ude00"); console.log("\ud83d" + "\ude00", "\ud83d" + "@" + "\ude00");
} }
expect_exact: 'console.log("\\ud83d"+"\\ude00","\\ud83d"+"@"+"\\ude00");' expect_exact: 'console.log("\\ud83d"+"\\ude00","\\ud83d"+"@"+"\\ude00");'
expect_stdout: "\ud83d\ude00 \ud83d@\ude00"
} }
issue_2242_4: { issue_2242_4: {
@@ -54,6 +132,7 @@ issue_2242_4: {
console.log("\ud83d" + "\ude00", "\ud83d" + "@" + "\ude00"); console.log("\ud83d" + "\ude00", "\ud83d" + "@" + "\ude00");
} }
expect_exact: 'console.log("\ud83d\ude00","\\ud83d@\\ude00");' expect_exact: 'console.log("\ud83d\ude00","\\ud83d@\\ude00");'
expect_stdout: "\ud83d\ude00 \ud83d@\ude00"
} }
issue_2569: { issue_2569: {

View File

@@ -0,0 +1,17 @@
if (x) foo();
if (x) foo(); else baz();
if (x) foo(); else if (y) bar(); else baz();
if (x) if (y) foo(); else bar(); else baz();
if (x) foo(); else if (y) bar(); else if (z) baz(); else moo();
function f() {
if (x) foo();
if (x) foo(); else baz();
if (x) foo(); else if (y) bar(); else baz();
if (x) if (y) foo(); else bar(); else baz();
if (x) foo(); else if (y) bar(); else if (z) baz(); else moo();
}

View File

@@ -1,17 +1 @@
if (x) foo(); if(x)foo();if(x)foo();else baz();if(x)foo();else if(y)bar();else baz();if(x)if(y)foo();else bar();else baz();if(x)foo();else if(y)bar();else if(z)baz();else moo();function f(){if(x)foo();if(x)foo();else baz();if(x)foo();else if(y)bar();else baz();if(x)if(y)foo();else bar();else baz();if(x)foo();else if(y)bar();else if(z)baz();else moo()}
if (x) foo(); else baz();
if (x) foo(); else if (y) bar(); else baz();
if (x) if (y) foo(); else bar(); else baz();
if (x) foo(); else if (y) bar(); else if (z) baz(); else moo();
function f() {
if (x) foo();
if (x) foo(); else baz();
if (x) foo(); else if (y) bar(); else baz();
if (x) if (y) foo(); else bar(); else baz();
if (x) foo(); else if (y) bar(); else if (z) baz(); else moo();
}

View File

@@ -1,7 +1,7 @@
var fs = require("fs"); var fs = require("fs");
var config = { var config = {
limit: 5000, limit: 10000,
timeout: function(limit) { timeout: function(limit) {
this.limit = limit; this.limit = limit;
} }
@@ -55,11 +55,11 @@ process.nextTick(function run() {
var elapsed = Date.now(); var elapsed = Date.now();
var timer; var timer;
var done = function() { var done = function() {
reset();
elapsed = Date.now() - elapsed; elapsed = Date.now() - elapsed;
if (elapsed > task.limit) { if (elapsed > task.limit) {
throw new Error("Timed out: " + elapsed + "ms > " + task.limit + "ms"); throw new Error("Timed out: " + elapsed + "ms > " + task.limit + "ms");
} }
reset();
log_titles(console.log, task.titles, green('\u221A ')); log_titles(console.log, task.titles, green('\u221A '));
process.nextTick(run); process.nextTick(run);
}; };

View File

@@ -176,7 +176,7 @@ describe("bin/uglifyjs", function() {
var command = uglifyjscmd + ' test/input/issue-1482/input.js -b'; var command = uglifyjscmd + ' test/input/issue-1482/input.js -b';
exec(command, function(err, stdout) { exec(command, function(err, stdout) {
if (err) throw err; if (err) throw err;
assert.strictEqual(stdout, read("test/input/issue-1482/default.js")); assert.strictEqual(stdout, read("test/input/issue-1482/beautify.js"));
done(); done();
}); });
}); });
@@ -188,6 +188,22 @@ describe("bin/uglifyjs", function() {
done(); done();
}); });
}); });
it("Should work with `--output-opts`", function(done) {
var command = uglifyjscmd + ' test/input/issue-1482/input.js -O';
exec(command, function(err, stdout) {
if (err) throw err;
assert.strictEqual(stdout, read("test/input/issue-1482/default.js"));
done();
});
});
it("Should fail when both --beautify & --output-opts are specified", function(done) {
var command = uglifyjscmd + " test/input/issue-520/input.js -bO";
exec(command, function(err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stderr, "ERROR: --beautify cannot be used with --output-opts\n");
done();
});
});
it("Should process inline source map", function(done) { it("Should process inline source map", function(done) {
var command = [ var command = [
uglifyjscmd, uglifyjscmd,

View File

@@ -69,13 +69,13 @@ describe("Directives", function() {
], ],
[ [
'"use \\\nstrict";"use strict";', '"use \\\nstrict";"use strict";',
[], [ "use strict" ],
[ "use strict", "use\nstrict", "use \nstrict", "use asm" ] [ "use\nstrict", "use \nstrict", "use asm" ]
], ],
[ [
'"\\76";', '"\\76";',
[], [ "\\76" ],
[ ">", "\\76" ] [ ">" ]
], ],
[ [
// no ; or newline // no ; or newline
@@ -106,13 +106,13 @@ describe("Directives", function() {
], ],
[ [
'function foo() {"use \\\nstrict";"use strict";', 'function foo() {"use \\\nstrict";"use strict";',
[], [ "use strict" ],
[ "use strict", "use\nstrict", "use \nstrict", "use asm" ] [ "use\nstrict", "use \nstrict", "use asm" ]
], ],
[ [
'var foo = function() {"\\76";', 'var foo = function() {"\\76";',
[], [ "\\76" ],
[ ">", "\\76" ] [ ">" ]
], ],
[ [
'var foo = function() {"use strict"', // no ; or newline 'var foo = function() {"use strict"', // no ; or newline
@@ -156,21 +156,24 @@ describe("Directives", function() {
}); });
}); });
}); });
it("Should test EXPECT_DIRECTIVE RegExp", function() { it("Should print semicolon to separate strings from directives", function() {
[ [
[ "", true ], [ "", ';"";' ],
[ "'test';", true ], [ '"test";', '"test";;"";' ],
[ "'test';;", true ], [ '"test";;', '"test";;"";' ],
[ "'tests';\n", true ], [ '"tests";\n', '"tests";;"";' ],
[ "'tests'", false ], [ '"tests"', '"tests";;"";' ],
[ "'tests'; \n\t", true ], [ '"tests"; \n\t', '"tests";;"";' ],
[ "'tests';\n\n", true ], [ '"tests";\n\n', '"tests";;"";' ],
[ "\n\n\"use strict\";\n\n", true ], [ '\n\n"use strict";\n\n', '"use strict";;"";' ],
].forEach(function(test) { ].forEach(function(test) {
var ast = UglifyJS.parse(test[0]);
ast.body.push(new UglifyJS.AST_SimpleStatement({
body: new UglifyJS.AST_String({ value: "" })
}));
var out = UglifyJS.OutputStream(); var out = UglifyJS.OutputStream();
out.print(test[0]); ast.print(out);
out.print_string("", null, true); assert.strictEqual(out.get(), test[1], test[0]);
assert.strictEqual(out.get() === test[0] + ';""', test[1], test[0]);
}); });
}); });
it("Should only print 2 semicolons spread over 2 lines in beautify mode", function() { it("Should only print 2 semicolons spread over 2 lines in beautify mode", function() {
@@ -178,8 +181,8 @@ describe("Directives", function() {
'"use strict";', '"use strict";',
"'use strict';", "'use strict';",
'"use strict";', '"use strict";',
'"use strict";;', '"use strict";',
"'use strict';", ";'use strict';",
"console.log('use strict');" "console.log('use strict');"
].join(""), { ].join(""), {
compress: false, compress: false,
@@ -201,19 +204,23 @@ describe("Directives", function() {
it("Should not add double semicolons in non-scoped block statements to avoid strings becoming directives", function() { it("Should not add double semicolons in non-scoped block statements to avoid strings becoming directives", function() {
[ [
[ [
'{"use\x20strict"}', '"use strict";"use\\x20strict";',
'"use strict";"use\\x20strict";'
],
[
'{"use\\x20strict"}',
'{"use strict"}' '{"use strict"}'
], ],
[ [
'function foo(){"use\x20strict";}', // Valid place for directives 'function foo(){"use\\x20strict";}', // Valid place for directives
'function foo(){"use strict"}' 'function foo(){"use\\x20strict"}'
], ],
[ [
'try{"use\x20strict"}catch(e){}finally{"use\x20strict"}', 'try{"use\\x20strict"}catch(e){}finally{"use\\x20strict"}',
'try{"use strict"}catch(e){}finally{"use strict"}' 'try{"use strict"}catch(e){}finally{"use strict"}'
], ],
[ [
'if(1){"use\x20strict"} else {"use strict"}', 'if(1){"use\\x20strict"} else {"use strict"}',
'if(1){"use strict"}else{"use strict"}' 'if(1){"use strict"}else{"use strict"}'
] ]
].forEach(function(test) { ].forEach(function(test) {
@@ -225,16 +232,6 @@ describe("Directives", function() {
assert.strictEqual(result.code, test[1], test[0]); assert.strictEqual(result.code, test[1], test[0]);
}); });
}); });
it("Should add double semicolon when relying on automatic semicolon insertion", function() {
var result = UglifyJS.minify('"use strict";"use\\x20strict";', {
compress: false,
output: {
semicolons: false
}
});
if (result.error) throw result.error;
assert.strictEqual(result.code, '"use strict";;"use strict"\n');
});
it("Should check quote style of directives", function() { it("Should check quote style of directives", function() {
[ [
// 0. Prefer double quotes, unless string contains more double quotes than single quotes // 0. Prefer double quotes, unless string contains more double quotes than single quotes
@@ -249,9 +246,9 @@ describe("Directives", function() {
'"use strict";' '"use strict";'
], ],
[ [
'"\\\'use strict\\\'";', // Not a directive as it contains quotes '"\\\'use strict\\\'";',
0, 0,
';"\'use strict\'";', '"\\\'use strict\\\'";',
], ],
[ [
"'\"use strict\"';", "'\"use strict\"';",
@@ -273,7 +270,7 @@ describe("Directives", function() {
'"\'use strict\'";', '"\'use strict\'";',
1, 1,
// Intentionally causes directive breakage at cost of less logic, usage should be rare anyway // Intentionally causes directive breakage at cost of less logic, usage should be rare anyway
"'\\'use strict\\'';", '"\'use strict\'";',
], ],
[ [
"'\\'use strict\\'';", // Not a valid directive "'\\'use strict\\'';", // Not a valid directive
@@ -305,7 +302,7 @@ describe("Directives", function() {
"'\"use strict\"';", "'\"use strict\"';",
2, 2,
// Intentionally causes directive breakage at cost of less logic, usage should be rare anyway // Intentionally causes directive breakage at cost of less logic, usage should be rare anyway
'"\\\"use strict\\\"";', "'\"use strict\"';",
], ],
[ [
'"\\"use strict\\"";', // Not a valid directive '"\\"use strict\\"";', // Not a valid directive
@@ -353,8 +350,7 @@ describe("Directives", function() {
[ [
// Nothing gets optimised in the compressor because "use asm" is the first statement // Nothing gets optimised in the compressor because "use asm" is the first statement
'"use asm";"use\\x20strict";1+1;', '"use asm";"use\\x20strict";1+1;',
// Yet, the parser noticed that "use strict" wasn't a directive '"use asm";"use\\x20strict";1+1;'
'"use asm";;"use strict";1+1;',
], ],
[ [
'function f(){ "use strict" }', 'function f(){ "use strict" }',

View File

@@ -59,13 +59,13 @@ describe("String literals", function() {
it("Should not throw error outside strict mode if string contains escaped octalIntegerLiteral", function() { it("Should not throw error outside strict mode if string contains escaped octalIntegerLiteral", function() {
var tests = [ var tests = [
['"\\76";', ';">";'], [ ';"\\76";', ';">";' ],
['"\\0"', '"\\0";'], [ ';"\\0";', ';"\\0";' ],
['"\\08"', '"\\x008";'], [ ';"\\08"', ';"\\x008";' ],
['"\\008"', '"\\x008";'], [ ';"\\008"', ';"\\x008";' ],
['"\\0008"', '"\\x008";'], [ ';"\\0008"', ';"\\x008";' ],
['"use strict" === "use strict";\n"\\76";', '"use strict"==="use strict";">";'], [ ';"use\\\n strict";\n"\\07";', ';"use strict";"\07";' ],
['"use\\\n strict";\n"\\07";', ';"use strict";"\07";'] [ '"use strict" === "use strict";\n"\\76";', '"use strict"==="use strict";">";' ],
]; ];
for (var test in tests) { for (var test in tests) {
@@ -75,8 +75,8 @@ describe("String literals", function() {
}); });
it("Should not throw error when digit is 8 or 9", function() { it("Should not throw error when digit is 8 or 9", function() {
assert.equal(UglifyJS.parse('"use strict";"\\08"').print_to_string(), '"use strict";"\\x008";'); assert.equal(UglifyJS.parse('"use strict";;"\\08"').print_to_string(), '"use strict";;"\\x008";');
assert.equal(UglifyJS.parse('"use strict";"\\09"').print_to_string(), '"use strict";"\\x009";'); assert.equal(UglifyJS.parse('"use strict";;"\\09"').print_to_string(), '"use strict";;"\\x009";');
}); });
it("Should not unescape unpaired surrogates", function() { it("Should not unescape unpaired surrogates", function() {
@@ -93,7 +93,7 @@ describe("String literals", function() {
for (; i <= 0xFFFF; i++) { for (; i <= 0xFFFF; i++) {
code.push("\\u" + i.toString(16)); code.push("\\u" + i.toString(16));
} }
code = '"' + code.join() + '"'; code = ';"' + code.join() + '"';
var normal = UglifyJS.minify(code, { var normal = UglifyJS.minify(code, {
compress: false, compress: false,
mangle: false, mangle: false,

View File

@@ -1089,6 +1089,20 @@ function log(options) {
} }
} }
function fuzzy_match(original, uglified) {
uglified = uglified.split(" ");
var i = uglified.length;
original = original.split(" ", i);
while (--i >= 0) {
if (original[i] === uglified[i]) continue;
var a = +original[i];
var b = +uglified[i];
if (Math.abs((b - a) / a) < 1e-10) continue;
return false;
}
return true;
}
var fallback_options = [ JSON.stringify({ var fallback_options = [ JSON.stringify({
compress: false, compress: false,
mangle: false mangle: false
@@ -1111,8 +1125,12 @@ for (var round = 1; round <= num_iterations; round++) {
uglify_code = uglify_code.code; uglify_code = uglify_code.code;
uglify_result = sandbox.run_code(uglify_code, o.toplevel); uglify_result = sandbox.run_code(uglify_code, o.toplevel);
ok = sandbox.same_stdout(original_result, uglify_result); ok = sandbox.same_stdout(original_result, uglify_result);
if (!ok && o.compress.unsafe_math) { if (!ok && typeof uglify_result == "string" && o.compress.unsafe_math) {
ok = sandbox.same_stdout(sandbox.run_code(original_code.replace(/( - 0\.1){3}/g, " - 0.3")), uglify_result, o.toplevel); ok = fuzzy_match(original_result, uglify_result);
if (!ok) {
var fuzzy_result = sandbox.run_code(original_code.replace(/( - 0\.1){3}/g, " - 0.3"));
ok = sandbox.same_stdout(fuzzy_result, uglify_result);
}
} }
} else { } else {
uglify_code = uglify_code.error; uglify_code = uglify_code.error;

View File

@@ -12,17 +12,16 @@ function spawn(endTime) {
], { ], {
stdio: [ "ignore", "pipe", "pipe" ] stdio: [ "ignore", "pipe", "pipe" ]
}).on("exit", respawn); }).on("exit", respawn);
var line = ""; var stdout = "";
child.stdout.on("data", function(data) { child.stdout.on("data", function(data) {
line += data; stdout += data;
}); });
child.stderr.once("data", function() { var stderr = "";
process.exitCode = 1; child.stderr.on("data", trap).pipe(process.stdout);
}).pipe(process.stdout);
var keepAlive = setInterval(function() { var keepAlive = setInterval(function() {
var end = line.lastIndexOf("\r"); var end = stdout.lastIndexOf("\r");
console.log(line.slice(line.lastIndexOf("\r", end - 1) + 1, end)); console.log(stdout.slice(stdout.lastIndexOf("\r", end - 1) + 1, end));
line = line.slice(end + 1); stdout = stdout.slice(end + 1);
}, ping); }, ping);
var timer = setTimeout(function() { var timer = setTimeout(function() {
clearInterval(keepAlive); clearInterval(keepAlive);
@@ -31,9 +30,17 @@ function spawn(endTime) {
}, endTime - Date.now()); }, endTime - Date.now());
function respawn() { function respawn() {
console.log(line); console.log(stdout.replace(/[^\r\n]*\r/g, ""));
clearInterval(keepAlive); clearInterval(keepAlive);
clearTimeout(timer); clearTimeout(timer);
spawn(endTime); spawn(endTime);
} }
function trap(data) {
stderr += data;
if (~stderr.indexOf("\nminify(options):\n")) {
process.exitCode = 1;
child.stderr.removeListener("data", trap);
}
}
} }