Compare commits

...

16 Commits

Author SHA1 Message Date
Alex Lam S.L
30a75049f5 v2.8.14 2017-03-19 15:24:57 +08:00
Alex Lam S.L
a3cc3a9b87 make expect_stdout work on Node.js 0.12 (#1623)
That particular version of Node.js has messed up error messages, so provide a version-specific workaround.

Also fixed an formatting issue which would cause `expect_stdout` to fail if error message contains excerpts of input.

Apply `expect_stdout` to more applicable tests.
2017-03-19 12:00:32 +08:00
Alex Lam S.L
96f8befdd7 fix commit 88fb83a (#1622)
The following is wrong:
    `a == (b ? a : c)` => `b`
Because:
- `b` may not be boolean
- `a` might have side effects
- `a == a` is not always `true` (think `NaN`)
- `a == c` is not always `false`
2017-03-19 11:59:42 +08:00
Alex Lam S.L
cd58635dcc fix AST_Binary.lift_sequences() (#1621)
Commit eab99a1c fails to account for side effects from compound assignments.
2017-03-19 03:04:22 +08:00
Alex Lam S.L
274331d0ea transform String.charAt() to index access (#1620)
Guarded by `unsafe` as `charAt()` can be overridden.
2017-03-19 02:17:15 +08:00
Alex Lam S.L
0489d6de64 handle runtime errors in expect_stdout (#1618)
allow test to pass if both `input` and `expect` throws the same kind of error
2017-03-18 02:33:51 +08:00
Alex Lam S.L
fb092839c2 fix top-level directives in compress tests (#1615)
`input` and `expect` are parsed as `AST_BlockStatement` which does not support `AST_Directive` by default.

Emulate that by transforming preceding `AST_SimpleStatement`s of `AST_String` into `AST_Directive`.
2017-03-18 01:56:15 +08:00
Christian Maughan Tegnér
b7c112eefe Add --in-source-map inline documentation (#1611) 2017-03-17 03:08:38 +08:00
Alex Lam S.L
b2b8a0d386 v2.8.13 2017-03-17 02:01:33 +08:00
Alex Lam S.L
ac40301813 fix chained evaluation (#1610)
`reduce_vars` enables substitution of variables but did not clone the value's `AST_Node`.

This confuses `collapse_vars` and result in invalid AST and subsequent crash.

fixes #1609
2017-03-17 00:26:48 +08:00
Alex Lam S.L
3563d8c09e extend test/run-tests.js to optionally execute uglified output (#1604)
fixes #1588
2017-03-16 23:20:06 +08:00
Alex Lam S.L
5ae04b3545 make collapse_vars consistent with toplevel (#1608)
fixes #1605
2017-03-16 13:22:26 +08:00
Alex Lam S.L
a80b228d8b fix hoist_vars on reduce_vars (#1607)
`hoist_vars` converts variable declarations into plain assignments, which then confuses `reduce_vars`

fixes #1606
2017-03-16 12:03:30 +08:00
Alex Lam S.L
cf4bf4ceb1 fix stack issues with AST_Node.evaluate() (#1603)
As patched in #1597, `make_node_from_constant()` makes inconsistent and sometimes incorrect calls to `optimize()` and `transform()`.

Fix those issues properly by changing the semantics of `evaluate()` and `make_node_from_constant()`, with the side effect that `evaluate()` no longer eagerly converts constant to `AST_Node`.
2017-03-16 01:02:59 +08:00
Alex Lam S.L
8223b2e0db fix AST_Node.optimize() (#1602)
Liberal use of `Compressor.transform()` and `AST_Node.optimize()` presents an issue for look-up operations like `TreeWalker.in_boolean_context()` and `TreeWalker.parent()`.

This is an incremental fix such that `AST_Node.optimize()` would now contain the correct stack information when called correctly.
2017-03-15 18:44:13 +08:00
Alex Lam S.L
381bd3836e minor clean-ups (#1600)
- remove obsolete optimisation in `AST_Binary` after #1477
- improve `TreeWalker.has_directive()` readability and resilience against multiple visits
2017-03-14 13:19:05 +08:00
32 changed files with 961 additions and 267 deletions

View File

@@ -68,7 +68,8 @@ The available options are:
--source-map-inline Write base64-encoded source map to the end of js output. --source-map-inline Write base64-encoded source map to the end of js output.
--in-source-map Input source map, useful if you're compressing --in-source-map Input source map, useful if you're compressing
JS that was generated from some other original JS that was generated from some other original
code. code. Specify "inline" if the source map is included
inline with the sources.
--screw-ie8 Use this flag if you don't wish to support --screw-ie8 Use this flag if you don't wish to support
Internet Explorer 6/7/8. Internet Explorer 6/7/8.
By default UglifyJS will not try to be IE-proof. By default UglifyJS will not try to be IE-proof.
@@ -200,9 +201,10 @@ compressed JS by mapping every token in the compiled JS to its original
location. location.
To use this feature you need to pass `--in-source-map To use this feature you need to pass `--in-source-map
/path/to/input/source.map`. Normally the input source map should also point /path/to/input/source.map` or `--in-source-map inline` if the source map is
to the file containing the generated JS, so if that's correct you can omit included inline with the sources. Normally the input source map should also
input files from the command line. point to the file containing the generated JS, so if that's correct you can
omit input files from the command line.
## Mangler options ## Mangler options

View File

@@ -984,8 +984,8 @@ TreeWalker.prototype = {
push: function (node) { push: function (node) {
if (node instanceof AST_Lambda) { if (node instanceof AST_Lambda) {
this.directives = Object.create(this.directives); this.directives = Object.create(this.directives);
} else if (node instanceof AST_Directive) { } else if (node instanceof AST_Directive && !this.directives[node.value]) {
this.directives[node.value] = this.directives[node.value] ? "up" : true; this.directives[node.value] = node;
} }
this.stack.push(node); this.stack.push(node);
}, },
@@ -1013,7 +1013,7 @@ TreeWalker.prototype = {
for (var i = 0; i < node.body.length; ++i) { for (var i = 0; i < node.body.length; ++i) {
var st = node.body[i]; var st = node.body[i];
if (!(st instanceof AST_Directive)) break; if (!(st instanceof AST_Directive)) break;
if (st.value == type) return true; if (st.value == type) return st;
} }
} }
}, },

View File

@@ -98,10 +98,10 @@ function Compressor(options, false_by_default) {
this.top_retain = function(def) { this.top_retain = function(def) {
return top_retain.test(def.name); return top_retain.test(def.name);
}; };
} else if (typeof top_retain === "function") { } else if (typeof top_retain == "function") {
this.top_retain = top_retain; this.top_retain = top_retain;
} else if (top_retain) { } else if (top_retain) {
if (typeof top_retain === "string") { if (typeof top_retain == "string") {
top_retain = top_retain.split(/,/); top_retain = top_retain.split(/,/);
} }
this.top_retain = function(def) { this.top_retain = function(def) {
@@ -151,14 +151,25 @@ merge(Compressor.prototype, {
node = node.hoist_declarations(this); node = node.hoist_declarations(this);
was_scope = true; was_scope = true;
} }
// Before https://github.com/mishoo/UglifyJS2/pull/1602 AST_Node.optimize()
// would call AST_Node.transform() if a different instance of AST_Node is
// produced after OPT().
// This corrupts TreeWalker.stack, which cause AST look-ups to malfunction.
// Migrate and defer all children's AST_Node.transform() to below, which
// will now happen after this parent AST_Node has been properly substituted
// thus gives a consistent AST snapshot.
descend(node, this); descend(node, this);
node = node.optimize(this); // Existing code relies on how AST_Node.optimize() worked, and omitting the
if (was_scope && node instanceof AST_Scope) { // following replacement call would result in degraded efficiency of both
node.drop_unused(this); // output and performance.
descend(node, this); descend(node, this);
var opt = node.optimize(this);
if (was_scope && opt instanceof AST_Scope) {
opt.drop_unused(this);
descend(opt, this);
} }
node._squeezed = true; if (opt === node) opt._squeezed = true;
return node; return opt;
} }
}); });
@@ -171,8 +182,7 @@ merge(Compressor.prototype, {
if (compressor.has_directive("use asm")) return self; if (compressor.has_directive("use asm")) return self;
var opt = optimizer(self, compressor); var opt = optimizer(self, compressor);
opt._optimized = true; opt._optimized = true;
if (opt === self) return opt; return opt;
return opt.transform(compressor);
}); });
}; };
@@ -384,7 +394,7 @@ merge(Compressor.prototype, {
return new ctor(props); return new ctor(props);
}; };
function make_node_from_constant(compressor, val, orig) { function make_node_from_constant(val, orig) {
switch (typeof val) { switch (typeof val) {
case "string": case "string":
return make_node(AST_String, orig, { return make_node(AST_String, orig, {
@@ -404,9 +414,9 @@ merge(Compressor.prototype, {
return make_node(AST_Number, orig, { value: val }); return make_node(AST_Number, orig, { value: val });
case "boolean": case "boolean":
return make_node(val ? AST_True : AST_False, orig).optimize(compressor); return make_node(val ? AST_True : AST_False, orig);
case "undefined": case "undefined":
return make_node(AST_Undefined, orig).transform(compressor); return make_node(AST_Undefined, orig);
default: default:
if (val === null) { if (val === null) {
return make_node(AST_Null, orig, { value: null }); return make_node(AST_Null, orig, { value: null });
@@ -511,6 +521,7 @@ merge(Compressor.prototype, {
var self = compressor.self(); var self = compressor.self();
var var_defs_removed = false; var var_defs_removed = false;
var toplevel = compressor.option("toplevel");
for (var stat_index = statements.length; --stat_index >= 0;) { for (var stat_index = statements.length; --stat_index >= 0;) {
var stat = statements[stat_index]; var stat = statements[stat_index];
if (stat instanceof AST_Definitions) continue; if (stat instanceof AST_Definitions) continue;
@@ -548,7 +559,8 @@ merge(Compressor.prototype, {
// Only interested in cases with just one reference to the variable. // Only interested in cases with just one reference to the variable.
var def = self.find_variable && self.find_variable(var_name); var def = self.find_variable && self.find_variable(var_name);
if (!def || !def.references || def.references.length !== 1 || var_name == "arguments") { if (!def || !def.references || def.references.length !== 1
|| var_name == "arguments" || (!toplevel && def.global)) {
side_effects_encountered = true; side_effects_encountered = true;
continue; continue;
} }
@@ -914,7 +926,7 @@ merge(Compressor.prototype, {
if (stat instanceof AST_LoopControl) { if (stat instanceof AST_LoopControl) {
var lct = compressor.loopcontrol_target(stat.label); var lct = compressor.loopcontrol_target(stat.label);
if ((stat instanceof AST_Break if ((stat instanceof AST_Break
&& lct instanceof AST_BlockStatement && !(lct instanceof AST_IterationStatement)
&& loop_body(lct) === self) || (stat instanceof AST_Continue && loop_body(lct) === self) || (stat instanceof AST_Continue
&& loop_body(lct) === self)) { && loop_body(lct) === self)) {
if (stat.label) { if (stat.label) {
@@ -1198,11 +1210,11 @@ merge(Compressor.prototype, {
} }
} }
}); });
function to_node(compressor, value, orig) { function to_node(value, orig) {
if (value instanceof AST_Node) return make_node(value.CTOR, orig, value); if (value instanceof AST_Node) return make_node(value.CTOR, orig, value);
if (Array.isArray(value)) return make_node(AST_Array, orig, { if (Array.isArray(value)) return make_node(AST_Array, orig, {
elements: value.map(function(value) { elements: value.map(function(value) {
return to_node(compressor, value, orig); return to_node(value, orig);
}) })
}); });
if (value && typeof value == "object") { if (value && typeof value == "object") {
@@ -1210,14 +1222,14 @@ merge(Compressor.prototype, {
for (var key in value) { for (var key in value) {
props.push(make_node(AST_ObjectKeyVal, orig, { props.push(make_node(AST_ObjectKeyVal, orig, {
key: key, key: key,
value: to_node(compressor, value[key], orig) value: to_node(value[key], orig)
})); }));
} }
return make_node(AST_Object, orig, { return make_node(AST_Object, orig, {
properties: props properties: props
}); });
} }
return make_node_from_constant(compressor, value, orig); return make_node_from_constant(value, orig);
} }
def(AST_Node, noop); def(AST_Node, noop);
def(AST_Dot, function(compressor, suffix){ def(AST_Dot, function(compressor, suffix){
@@ -1228,7 +1240,7 @@ merge(Compressor.prototype, {
var name; var name;
var defines = compressor.option("global_defs"); var defines = compressor.option("global_defs");
if (defines && HOP(defines, (name = this.name + suffix))) { if (defines && HOP(defines, (name = this.name + suffix))) {
var node = to_node(compressor, defines[name], this); var node = to_node(defines[name], this);
var top = compressor.find_parent(AST_Toplevel); var top = compressor.find_parent(AST_Toplevel);
node.walk(new TreeWalker(function(node) { node.walk(new TreeWalker(function(node) {
if (node instanceof AST_SymbolRef) { if (node instanceof AST_SymbolRef) {
@@ -1243,45 +1255,40 @@ merge(Compressor.prototype, {
node.DEFMETHOD("_find_defs", func); node.DEFMETHOD("_find_defs", func);
}); });
function best_of(ast1, ast2) { function best_of_expression(ast1, ast2) {
return ast1.print_to_string().length > return ast1.print_to_string().length >
ast2.print_to_string().length ast2.print_to_string().length
? ast2 : ast1; ? ast2 : ast1;
} }
function best_of_statement(ast1, ast2) { function best_of_statement(ast1, ast2) {
return best_of(make_node(AST_SimpleStatement, ast1, { return best_of_expression(make_node(AST_SimpleStatement, ast1, {
body: ast1 body: ast1
}), make_node(AST_SimpleStatement, ast2, { }), make_node(AST_SimpleStatement, ast2, {
body: ast2 body: ast2
})).body; })).body;
} }
function best_of(compressor, ast1, ast2) {
return (first_in_statement(compressor) ? best_of_statement : best_of_expression)(ast1, ast2);
}
// methods to evaluate a constant expression // methods to evaluate a constant expression
(function (def){ (function (def){
// The evaluate method returns an array with one or two // If the node has been successfully reduced to a constant,
// elements. If the node has been successfully reduced to a // then its value is returned; otherwise the element itself
// constant, then the second element tells us the value; // is returned.
// otherwise the second element is missing. The first element // They can be distinguished as constant value is never a
// of the array is always an AST_Node descendant; if // descendant of AST_Node.
// evaluation was successful it's a node that represents the
// constant; otherwise it's the original or a replacement node.
AST_Node.DEFMETHOD("evaluate", function(compressor){ AST_Node.DEFMETHOD("evaluate", function(compressor){
if (!compressor.option("evaluate")) return [ this ]; if (!compressor.option("evaluate")) return this;
var val;
try { try {
val = this._eval(compressor); var val = this._eval(compressor);
return !val || val instanceof RegExp || typeof val != "object" ? val : this;
} catch(ex) { } catch(ex) {
if (ex !== def) throw ex; if (ex !== def) throw ex;
return [ this ]; return this;
} }
var node;
try {
node = make_node_from_constant(compressor, val, this);
} catch(ex) {
return [ this ];
}
return [ best_of(node, this), val ];
}); });
var unaryPrefix = makePredicate("! ~ - +"); var unaryPrefix = makePredicate("! ~ - +");
AST_Node.DEFMETHOD("is_constant", function(){ AST_Node.DEFMETHOD("is_constant", function(){
@@ -1319,8 +1326,8 @@ merge(Compressor.prototype, {
})); }));
} }
var result = this.evaluate(compressor); var result = this.evaluate(compressor);
if (result.length > 1) { if (result !== this) {
return result[1]; return result;
} }
throw new Error(string_template("Cannot evaluate constant [{file}:{line},{col}]", this.start)); throw new Error(string_template("Cannot evaluate constant [{file}:{line},{col}]", this.start));
}); });
@@ -1480,9 +1487,9 @@ merge(Compressor.prototype, {
var stat = make_node(AST_SimpleStatement, alt, { var stat = make_node(AST_SimpleStatement, alt, {
body: alt body: alt
}); });
return best_of(negated, stat) === stat ? alt : negated; return best_of_expression(negated, stat) === stat ? alt : negated;
} }
return best_of(negated, alt); return best_of_expression(negated, alt);
} }
def(AST_Node, function(){ def(AST_Node, function(){
return basic_negation(this); return basic_negation(this);
@@ -1646,8 +1653,8 @@ merge(Compressor.prototype, {
return thing && thing.aborts(); return thing && thing.aborts();
}; };
(function(def){ (function(def){
def(AST_Statement, function(){ return null }); def(AST_Statement, return_null);
def(AST_Jump, function(){ return this }); def(AST_Jump, return_this);
function block_aborts(){ function block_aborts(){
var n = this.body.length; var n = this.body.length;
return n > 0 && aborts(this.body[n - 1]); return n > 0 && aborts(this.body[n - 1]);
@@ -1664,7 +1671,7 @@ merge(Compressor.prototype, {
/* -----[ optimizers ]----- */ /* -----[ optimizers ]----- */
OPT(AST_Directive, function(self, compressor){ OPT(AST_Directive, function(self, compressor){
if (compressor.has_directive(self.value) === "up") { if (compressor.has_directive(self.value) !== self) {
return make_node(AST_EmptyStatement, self); return make_node(AST_EmptyStatement, self);
} }
return self; return self;
@@ -1981,7 +1988,7 @@ merge(Compressor.prototype, {
vars.set(def.name.name, def); vars.set(def.name.name, def);
++vars_found; ++vars_found;
}); });
var seq = node.to_assignments(); var seq = node.to_assignments(compressor);
var p = tt.parent(); var p = tt.parent();
if (p instanceof AST_ForIn && p.init === node) { if (p instanceof AST_ForIn && p.init === node) {
if (seq == null) { if (seq == null) {
@@ -2077,14 +2084,6 @@ merge(Compressor.prototype, {
// drop_side_effect_free() // drop_side_effect_free()
// remove side-effect-free parts which only affects return value // remove side-effect-free parts which only affects return value
(function(def){ (function(def){
function return_this() {
return this;
}
function return_null() {
return null;
}
// Drop side-effect-free elements from an array of expressions. // Drop side-effect-free elements from an array of expressions.
// Returns an array of expressions with side-effects or null // Returns an array of expressions with side-effects or null
// if all elements were dropped. Note: original array may be // if all elements were dropped. Note: original array may be
@@ -2241,27 +2240,24 @@ merge(Compressor.prototype, {
}); });
OPT(AST_DWLoop, function(self, compressor){ OPT(AST_DWLoop, function(self, compressor){
var cond = self.condition.evaluate(compressor);
self.condition = cond[0];
if (!compressor.option("loops")) return self; if (!compressor.option("loops")) return self;
if (cond.length > 1) { var cond = self.condition.evaluate(compressor);
if (cond[1]) { if (cond !== self.condition) {
if (cond) {
return make_node(AST_For, self, { return make_node(AST_For, self, {
body: self.body body: self.body
}); });
} else if (self instanceof AST_While) { } else if (compressor.option("dead_code") && self instanceof AST_While) {
if (compressor.option("dead_code")) { var a = [];
var a = []; extract_declarations_from_unreachable_code(compressor, self.body, a);
extract_declarations_from_unreachable_code(compressor, self.body, a); return make_node(AST_BlockStatement, self, { body: a });
return make_node(AST_BlockStatement, self, { body: a });
}
} else { } else {
// self instanceof AST_Do cond = make_node_from_constant(cond, self.condition).transform(compressor);
return self; self.condition = best_of_expression(cond, self.condition);
} }
} }
if (self instanceof AST_While) { if (self instanceof AST_While) {
return make_node(AST_For, self, self).transform(compressor); return make_node(AST_For, self, self).optimize(compressor);
} }
return self; return self;
}); });
@@ -2283,7 +2279,7 @@ merge(Compressor.prototype, {
var first = self.body instanceof AST_BlockStatement ? self.body.body[0] : self.body; var first = self.body instanceof AST_BlockStatement ? self.body.body[0] : self.body;
if (first instanceof AST_If) { if (first instanceof AST_If) {
if (first.body instanceof AST_Break if (first.body instanceof AST_Break
&& compressor.loopcontrol_target(first.body.label) === self) { && compressor.loopcontrol_target(first.body.label) === compressor.self()) {
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,
@@ -2296,7 +2292,7 @@ merge(Compressor.prototype, {
drop_it(first.alternative); drop_it(first.alternative);
} }
else if (first.alternative instanceof AST_Break else if (first.alternative instanceof AST_Break
&& compressor.loopcontrol_target(first.alternative.label) === self) { && compressor.loopcontrol_target(first.alternative.label) === compressor.self()) {
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,
@@ -2312,27 +2308,25 @@ merge(Compressor.prototype, {
}; };
OPT(AST_For, function(self, compressor){ OPT(AST_For, function(self, compressor){
var cond = self.condition;
if (cond) {
cond = cond.evaluate(compressor);
self.condition = cond[0];
}
if (!compressor.option("loops")) return self; if (!compressor.option("loops")) return self;
if (cond) { if (self.condition) {
if (cond.length > 1 && !cond[1]) { var cond = self.condition.evaluate(compressor);
if (compressor.option("dead_code")) { if (compressor.option("dead_code") && !cond) {
var a = []; var a = [];
if (self.init instanceof AST_Statement) { if (self.init instanceof AST_Statement) {
a.push(self.init); a.push(self.init);
}
else if (self.init) {
a.push(make_node(AST_SimpleStatement, self.init, {
body: self.init
}));
}
extract_declarations_from_unreachable_code(compressor, self.body, a);
return make_node(AST_BlockStatement, self, { body: a });
} }
else if (self.init) {
a.push(make_node(AST_SimpleStatement, self.init, {
body: self.init
}));
}
extract_declarations_from_unreachable_code(compressor, self.body, a);
return make_node(AST_BlockStatement, self, { body: a });
}
if (cond !== self.condition) {
cond = make_node_from_constant(cond, self.condition).transform(compressor);
self.condition = best_of_expression(cond, self.condition);
} }
} }
if_break_in_loop(self, compressor); if_break_in_loop(self, compressor);
@@ -2348,9 +2342,8 @@ merge(Compressor.prototype, {
// “has no side effects”; also it doesn't work for cases like // “has no side effects”; also it doesn't work for cases like
// `x && true`, though it probably should. // `x && true`, though it probably should.
var cond = self.condition.evaluate(compressor); var cond = self.condition.evaluate(compressor);
self.condition = cond[0]; if (cond !== self.condition) {
if (cond.length > 1) { if (cond) {
if (cond[1]) {
compressor.warn("Condition always true [{file}:{line},{col}]", self.condition.start); compressor.warn("Condition always true [{file}:{line},{col}]", self.condition.start);
if (compressor.option("dead_code")) { if (compressor.option("dead_code")) {
var a = []; var a = [];
@@ -2358,7 +2351,7 @@ merge(Compressor.prototype, {
extract_declarations_from_unreachable_code(compressor, self.alternative, a); extract_declarations_from_unreachable_code(compressor, self.alternative, a);
} }
a.push(self.body); a.push(self.body);
return make_node(AST_BlockStatement, self, { body: a }).transform(compressor); return make_node(AST_BlockStatement, self, { body: a }).optimize(compressor);
} }
} else { } else {
compressor.warn("Condition always false [{file}:{line},{col}]", self.condition.start); compressor.warn("Condition always false [{file}:{line},{col}]", self.condition.start);
@@ -2366,9 +2359,11 @@ merge(Compressor.prototype, {
var a = []; var a = [];
extract_declarations_from_unreachable_code(compressor, self.body, a); extract_declarations_from_unreachable_code(compressor, self.body, a);
if (self.alternative) a.push(self.alternative); if (self.alternative) a.push(self.alternative);
return make_node(AST_BlockStatement, self, { body: a }).transform(compressor); return make_node(AST_BlockStatement, self, { body: a }).optimize(compressor);
} }
} }
cond = make_node_from_constant(cond, self.condition).transform(compressor);
self.condition = best_of_expression(cond, self.condition);
} }
var negated = self.condition.negate(compressor); var negated = self.condition.negate(compressor);
var self_condition_length = self.condition.print_to_string().length; var self_condition_length = self.condition.print_to_string().length;
@@ -2385,8 +2380,8 @@ merge(Compressor.prototype, {
} }
if (is_empty(self.body) && is_empty(self.alternative)) { if (is_empty(self.body) && is_empty(self.alternative)) {
return make_node(AST_SimpleStatement, self.condition, { return make_node(AST_SimpleStatement, self.condition, {
body: self.condition body: self.condition.clone()
}).transform(compressor); }).optimize(compressor);
} }
if (self.body instanceof AST_SimpleStatement if (self.body instanceof AST_SimpleStatement
&& self.alternative instanceof AST_SimpleStatement) { && self.alternative instanceof AST_SimpleStatement) {
@@ -2396,7 +2391,7 @@ merge(Compressor.prototype, {
consequent : statement_to_expression(self.body), consequent : statement_to_expression(self.body),
alternative : statement_to_expression(self.alternative) alternative : statement_to_expression(self.alternative)
}) })
}).transform(compressor); }).optimize(compressor);
} }
if (is_empty(self.alternative) && self.body instanceof AST_SimpleStatement) { if (is_empty(self.alternative) && self.body instanceof AST_SimpleStatement) {
if (self_condition_length === negated_length && !negated_is_best if (self_condition_length === negated_length && !negated_is_best
@@ -2412,14 +2407,14 @@ merge(Compressor.prototype, {
left : negated, left : negated,
right : statement_to_expression(self.body) right : statement_to_expression(self.body)
}) })
}).transform(compressor); }).optimize(compressor);
return make_node(AST_SimpleStatement, self, { return make_node(AST_SimpleStatement, self, {
body: make_node(AST_Binary, self, { body: make_node(AST_Binary, self, {
operator : "&&", operator : "&&",
left : self.condition, left : self.condition,
right : statement_to_expression(self.body) right : statement_to_expression(self.body)
}) })
}).transform(compressor); }).optimize(compressor);
} }
if (self.body instanceof AST_EmptyStatement if (self.body instanceof AST_EmptyStatement
&& self.alternative && self.alternative
@@ -2430,7 +2425,7 @@ merge(Compressor.prototype, {
left : self.condition, left : self.condition,
right : statement_to_expression(self.alternative) right : statement_to_expression(self.alternative)
}) })
}).transform(compressor); }).optimize(compressor);
} }
if (self.body instanceof AST_Exit if (self.body instanceof AST_Exit
&& self.alternative instanceof AST_Exit && self.alternative instanceof AST_Exit
@@ -2440,18 +2435,21 @@ merge(Compressor.prototype, {
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),
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 (self.body instanceof AST_If if (self.body instanceof AST_If
&& !self.body.alternative && !self.body.alternative
&& !self.alternative) { && !self.alternative) {
self.condition = make_node(AST_Binary, self.condition, { self = make_node(AST_If, self, {
operator: "&&", condition: make_node(AST_Binary, self.condition, {
left: self.condition, operator: "&&",
right: self.body.condition left: self.condition,
}).transform(compressor); right: self.body.condition
self.body = self.body.body; }),
body: self.body.body,
alternative: null
});
} }
if (aborts(self.body)) { if (aborts(self.body)) {
if (self.alternative) { if (self.alternative) {
@@ -2459,7 +2457,7 @@ merge(Compressor.prototype, {
self.alternative = null; self.alternative = null;
return make_node(AST_BlockStatement, self, { return make_node(AST_BlockStatement, self, {
body: [ self, alt ] body: [ self, alt ]
}).transform(compressor); }).optimize(compressor);
} }
} }
if (aborts(self.alternative)) { if (aborts(self.alternative)) {
@@ -2469,7 +2467,7 @@ merge(Compressor.prototype, {
self.alternative = null; self.alternative = null;
return make_node(AST_BlockStatement, self, { return make_node(AST_BlockStatement, self, {
body: [ self, body ] body: [ self, body ]
}).transform(compressor); }).optimize(compressor);
} }
return self; return self;
}); });
@@ -2493,12 +2491,12 @@ merge(Compressor.prototype, {
} }
break; break;
} }
var exp = self.expression.evaluate(compressor); var value = self.expression.evaluate(compressor);
out: if (exp.length == 2) try { out: if (value !== self.expression) try {
// constant expression // constant expression
self.expression = exp[0]; var expression = make_node_from_constant(value, self.expression);
self.expression = best_of_expression(expression, self.expression);
if (!compressor.option("dead_code")) break out; if (!compressor.option("dead_code")) break out;
var value = exp[1];
var in_if = false; var in_if = false;
var in_block = false; var in_block = false;
var started = false; var started = false;
@@ -2545,11 +2543,11 @@ merge(Compressor.prototype, {
if (stopped) return MAP.skip; if (stopped) return MAP.skip;
if (node instanceof AST_Case) { if (node instanceof AST_Case) {
var exp = node.expression.evaluate(compressor); var exp = node.expression.evaluate(compressor);
if (exp.length < 2) { if (exp === node.expression) {
// got a case with non-constant expression, baling out // got a case with non-constant expression, baling out
throw self; throw self;
} }
if (exp[1] === value || started) { if (exp === value || started) {
started = true; started = true;
if (aborts(node)) stopped = true; if (aborts(node)) stopped = true;
descend(node, this); descend(node, this);
@@ -2583,7 +2581,8 @@ merge(Compressor.prototype, {
this.definitions.forEach(function(def){ def.value = null }); this.definitions.forEach(function(def){ def.value = null });
}); });
AST_Definitions.DEFMETHOD("to_assignments", function(){ AST_Definitions.DEFMETHOD("to_assignments", function(compressor){
var reduce_vars = compressor.option("reduce_vars");
var assignments = this.definitions.reduce(function(a, def){ var assignments = this.definitions.reduce(function(a, def){
if (def.value) { if (def.value) {
var name = make_node(AST_SymbolRef, def.name, def.name); var name = make_node(AST_SymbolRef, def.name, def.name);
@@ -2592,6 +2591,7 @@ merge(Compressor.prototype, {
left : name, left : name,
right : def.value right : def.value
})); }));
if (reduce_vars) name.definition().fixed = false;
} }
return a; return a;
}, []); }, []);
@@ -2656,7 +2656,7 @@ merge(Compressor.prototype, {
if (self.args.length != 1) { if (self.args.length != 1) {
return make_node(AST_Array, self, { return make_node(AST_Array, self, {
elements: self.args elements: self.args
}).transform(compressor); }).optimize(compressor);
} }
break; break;
case "Object": case "Object":
@@ -2674,7 +2674,7 @@ merge(Compressor.prototype, {
left: self.args[0], left: self.args[0],
operator: "+", operator: "+",
right: make_node(AST_String, self, { value: "" }) right: make_node(AST_String, self, { value: "" })
}).transform(compressor); }).optimize(compressor);
break; break;
case "Number": case "Number":
if (self.args.length == 0) return make_node(AST_Number, self, { if (self.args.length == 0) return make_node(AST_Number, self, {
@@ -2683,7 +2683,7 @@ merge(Compressor.prototype, {
if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, { if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, {
expression: self.args[0], expression: self.args[0],
operator: "+" operator: "+"
}).transform(compressor); }).optimize(compressor);
case "Boolean": case "Boolean":
if (self.args.length == 0) return make_node(AST_False, self); if (self.args.length == 0) return make_node(AST_False, self);
if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, { if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, {
@@ -2692,7 +2692,7 @@ merge(Compressor.prototype, {
operator: "!" operator: "!"
}), }),
operator: "!" operator: "!"
}).transform(compressor); }).optimize(compressor);
break; break;
case "Function": case "Function":
// new Function() => function(){} // new Function() => function(){}
@@ -2757,21 +2757,20 @@ merge(Compressor.prototype, {
left: make_node(AST_String, self, { value: "" }), left: make_node(AST_String, self, { value: "" }),
operator: "+", operator: "+",
right: exp.expression right: exp.expression
}).transform(compressor); }).optimize(compressor);
} }
else if (exp instanceof AST_Dot && exp.expression instanceof AST_Array && exp.property == "join") EXIT: { else if (exp instanceof AST_Dot && exp.expression instanceof AST_Array && exp.property == "join") EXIT: {
var separator; var separator;
if (self.args.length > 0) { if (self.args.length > 0) {
separator = self.args[0].evaluate(compressor); separator = self.args[0].evaluate(compressor);
if (separator.length < 2) break EXIT; // not a constant if (separator === self.args[0]) break EXIT; // not a constant
separator = separator[1];
} }
var elements = []; var elements = [];
var consts = []; var consts = [];
exp.expression.elements.forEach(function(el) { exp.expression.elements.forEach(function(el) {
el = el.evaluate(compressor); var value = el.evaluate(compressor);
if (el.length > 1) { if (value !== el) {
consts.push(el[1]); consts.push(value);
} else { } else {
if (consts.length > 0) { if (consts.length > 0) {
elements.push(make_node(AST_String, self, { elements.push(make_node(AST_String, self, {
@@ -2779,7 +2778,7 @@ merge(Compressor.prototype, {
})); }));
consts.length = 0; consts.length = 0;
} }
elements.push(el[0]); elements.push(el);
} }
}); });
if (consts.length > 0) { if (consts.length > 0) {
@@ -2812,7 +2811,7 @@ merge(Compressor.prototype, {
left : prev, left : prev,
right : el right : el
}); });
}, first).transform(compressor); }, first).optimize(compressor);
} }
// need this awkward cloning to not affect original element // need this awkward cloning to not affect original element
// best_of will decide which one to get through. // best_of will decide which one to get through.
@@ -2820,7 +2819,17 @@ merge(Compressor.prototype, {
node.expression = node.expression.clone(); node.expression = node.expression.clone();
node.expression.expression = node.expression.expression.clone(); node.expression.expression = node.expression.expression.clone();
node.expression.expression.elements = elements; node.expression.expression.elements = elements;
return best_of(self, node); return best_of(compressor, self, node);
}
else if (exp instanceof AST_Dot && exp.expression.is_string(compressor) && exp.property == "charAt") {
var arg = self.args[0];
var index = arg ? arg.evaluate(compressor) : 0;
if (index !== arg) {
return make_node(AST_Sub, exp, {
expression: exp.expression,
property: make_node_from_constant(index | 0, arg || exp)
}).optimize(compressor);
}
} }
} }
if (exp instanceof AST_Function) { if (exp instanceof AST_Function) {
@@ -2966,8 +2975,7 @@ merge(Compressor.prototype, {
return e.expression; return e.expression;
} }
if (e instanceof AST_Binary) { if (e instanceof AST_Binary) {
var statement = first_in_statement(compressor); self = best_of(compressor, self, e.negate(compressor, first_in_statement(compressor)));
self = (statement ? best_of_statement : best_of)(self, e.negate(compressor, statement));
} }
break; break;
case "typeof": case "typeof":
@@ -2980,17 +2988,17 @@ merge(Compressor.prototype, {
}).optimize(compressor); }).optimize(compressor);
} }
} }
return self.evaluate(compressor)[0]; // avoids infinite recursion of numerals
if (self.operator != "-" || !(self.expression instanceof AST_Number)) {
var ev = self.evaluate(compressor);
if (ev !== self) {
ev = make_node_from_constant(ev, self).optimize(compressor);
return best_of(compressor, ev, self);
}
}
return self;
}); });
function has_side_effects_or_prop_access(node, compressor) {
var save_pure_getters = compressor.option("pure_getters");
compressor.options.pure_getters = false;
var ret = node.has_side_effects(compressor);
compressor.options.pure_getters = save_pure_getters;
return ret;
}
AST_Binary.DEFMETHOD("lift_sequences", function(compressor){ AST_Binary.DEFMETHOD("lift_sequences", function(compressor){
if (compressor.option("sequences")) { if (compressor.option("sequences")) {
if (this.left instanceof AST_Seq) { if (this.left instanceof AST_Seq) {
@@ -2998,18 +3006,23 @@ merge(Compressor.prototype, {
var x = seq.to_array(); var x = seq.to_array();
this.left = x.pop(); this.left = x.pop();
x.push(this); x.push(this);
seq = AST_Seq.from_array(x).transform(compressor); return AST_Seq.from_array(x).optimize(compressor);
return seq;
} }
if (this.right instanceof AST_Seq if (this.right instanceof AST_Seq && !this.left.has_side_effects(compressor)) {
&& this instanceof AST_Assign var assign = this.operator == "=" && this.left instanceof AST_SymbolRef;
&& !has_side_effects_or_prop_access(this.left, compressor)) { var root = this.right;
var seq = this.right; var cursor, seq = root;
var x = seq.to_array(); while (assign || !seq.car.has_side_effects(compressor)) {
this.right = x.pop(); cursor = seq;
x.push(this); if (seq.cdr instanceof AST_Seq) {
seq = AST_Seq.from_array(x).transform(compressor); seq = seq.cdr;
return seq; } else break;
}
if (cursor) {
this.right = cursor.cdr;
cursor.cdr = this;
return root.optimize(compressor);
}
} }
} }
return this; return this;
@@ -3018,16 +3031,6 @@ merge(Compressor.prototype, {
var commutativeOperators = makePredicate("== === != !== * & | ^"); var commutativeOperators = makePredicate("== === != !== * & | ^");
OPT(AST_Binary, function(self, compressor){ OPT(AST_Binary, function(self, compressor){
var lhs = self.left.evaluate(compressor);
var rhs = self.right.evaluate(compressor);
if (lhs.length > 1 && lhs[0].is_constant() !== self.left.is_constant()
|| rhs.length > 1 && rhs[0].is_constant() !== self.right.is_constant()) {
return make_node(AST_Binary, self, {
operator: self.operator,
left: lhs[0],
right: rhs[0]
}).optimize(compressor);
}
function reversible() { function reversible() {
return self.left instanceof AST_Constant return self.left instanceof AST_Constant
|| self.right instanceof AST_Constant || self.right instanceof AST_Constant
@@ -3054,32 +3057,6 @@ merge(Compressor.prototype, {
reverse(); reverse();
} }
} }
if (/^[!=]==?$/.test(self.operator)) {
if (self.left instanceof AST_SymbolRef && self.right instanceof AST_Conditional) {
if (self.right.consequent instanceof AST_SymbolRef
&& self.right.consequent.definition() === self.left.definition()) {
if (/^==/.test(self.operator)) return self.right.condition;
if (/^!=/.test(self.operator)) return self.right.condition.negate(compressor);
}
if (self.right.alternative instanceof AST_SymbolRef
&& self.right.alternative.definition() === self.left.definition()) {
if (/^==/.test(self.operator)) return self.right.condition.negate(compressor);
if (/^!=/.test(self.operator)) return self.right.condition;
}
}
if (self.right instanceof AST_SymbolRef && self.left instanceof AST_Conditional) {
if (self.left.consequent instanceof AST_SymbolRef
&& self.left.consequent.definition() === self.right.definition()) {
if (/^==/.test(self.operator)) return self.left.condition;
if (/^!=/.test(self.operator)) return self.left.condition.negate(compressor);
}
if (self.left.alternative instanceof AST_SymbolRef
&& self.left.alternative.definition() === self.right.definition()) {
if (/^==/.test(self.operator)) return self.left.condition.negate(compressor);
if (/^!=/.test(self.operator)) return self.left.condition;
}
}
}
} }
self = self.lift_sequences(compressor); self = self.lift_sequences(compressor);
if (compressor.option("comparisons")) switch (self.operator) { if (compressor.option("comparisons")) switch (self.operator) {
@@ -3112,48 +3089,48 @@ merge(Compressor.prototype, {
case "&&": case "&&":
var ll = self.left.evaluate(compressor); var ll = self.left.evaluate(compressor);
var rr = self.right.evaluate(compressor); var rr = self.right.evaluate(compressor);
if ((ll.length > 1 && !ll[1]) || (rr.length > 1 && !rr[1])) { if (!ll || !rr) {
compressor.warn("Boolean && always false [{file}:{line},{col}]", self.start); compressor.warn("Boolean && always false [{file}:{line},{col}]", self.start);
return make_node(AST_Seq, self, { return make_node(AST_Seq, self, {
car: self.left, car: self.left,
cdr: make_node(AST_False, self) cdr: make_node(AST_False, self)
}).optimize(compressor); }).optimize(compressor);
} }
if (ll.length > 1 && ll[1]) { if (ll !== self.left && ll) {
return rr[0]; return self.right.optimize(compressor);
} }
if (rr.length > 1 && rr[1]) { if (rr !== self.right && rr) {
return ll[0]; return self.left.optimize(compressor);
} }
break; break;
case "||": case "||":
var ll = self.left.evaluate(compressor); var ll = self.left.evaluate(compressor);
var rr = self.right.evaluate(compressor); var rr = self.right.evaluate(compressor);
if ((ll.length > 1 && ll[1]) || (rr.length > 1 && rr[1])) { if (ll !== self.left && ll || rr !== self.right && rr) {
compressor.warn("Boolean || always true [{file}:{line},{col}]", self.start); compressor.warn("Boolean || always true [{file}:{line},{col}]", self.start);
return make_node(AST_Seq, self, { return make_node(AST_Seq, self, {
car: self.left, car: self.left,
cdr: make_node(AST_True, self) cdr: make_node(AST_True, self)
}).optimize(compressor); }).optimize(compressor);
} }
if (ll.length > 1 && !ll[1]) { if (!ll) {
return rr[0]; return self.right.optimize(compressor);
} }
if (rr.length > 1 && !rr[1]) { if (!rr) {
return ll[0]; return self.left.optimize(compressor);
} }
break; break;
case "+": case "+":
var ll = self.left.evaluate(compressor); var ll = self.left.evaluate(compressor);
var rr = self.right.evaluate(compressor); var rr = self.right.evaluate(compressor);
if (ll.length > 1 && ll[0] instanceof AST_String && ll[1]) { if (ll && typeof ll == "string") {
compressor.warn("+ in boolean context always true [{file}:{line},{col}]", self.start); compressor.warn("+ in boolean context always true [{file}:{line},{col}]", self.start);
return make_node(AST_Seq, self, { return make_node(AST_Seq, self, {
car: self.right, car: self.right,
cdr: make_node(AST_True, self) cdr: make_node(AST_True, self)
}).optimize(compressor); }).optimize(compressor);
} }
if (rr.length > 1 && rr[0] instanceof AST_String && rr[1]) { if (rr && typeof rr == "string") {
compressor.warn("+ in boolean context always true [{file}:{line},{col}]", self.start); compressor.warn("+ in boolean context always true [{file}:{line},{col}]", self.start);
return make_node(AST_Seq, self, { return make_node(AST_Seq, self, {
car: self.left, car: self.left,
@@ -3165,12 +3142,11 @@ merge(Compressor.prototype, {
if (compressor.option("comparisons") && self.is_boolean()) { if (compressor.option("comparisons") && self.is_boolean()) {
if (!(compressor.parent() instanceof AST_Binary) if (!(compressor.parent() instanceof AST_Binary)
|| compressor.parent() instanceof AST_Assign) { || compressor.parent() instanceof AST_Assign) {
var statement = first_in_statement(compressor);
var negated = make_node(AST_UnaryPrefix, self, { var negated = make_node(AST_UnaryPrefix, self, {
operator: "!", operator: "!",
expression: self.negate(compressor, statement) expression: self.negate(compressor, first_in_statement(compressor))
}); });
self = (statement ? best_of_statement : best_of)(self, negated); self = best_of(compressor, self, negated);
} }
if (compressor.option("unsafe_comps")) { if (compressor.option("unsafe_comps")) {
switch (self.operator) { switch (self.operator) {
@@ -3322,9 +3298,9 @@ merge(Compressor.prototype, {
}); });
if (self.right instanceof AST_Constant if (self.right instanceof AST_Constant
&& !(self.left instanceof AST_Constant)) { && !(self.left instanceof AST_Constant)) {
self = best_of(reversed, self); self = best_of(compressor, reversed, self);
} else { } else {
self = best_of(self, reversed); self = best_of(compressor, self, reversed);
} }
} }
if (associative && self.is_number(compressor)) { if (associative && self.is_number(compressor)) {
@@ -3421,7 +3397,12 @@ merge(Compressor.prototype, {
self.right = self.right.right; self.right = self.right.right;
return self.transform(compressor); return self.transform(compressor);
} }
return self.evaluate(compressor)[0]; var ev = self.evaluate(compressor);
if (ev !== self) {
ev = make_node_from_constant(ev, self).optimize(compressor);
return best_of(compressor, ev, self);
}
return self;
}); });
OPT(AST_SymbolRef, function(self, compressor){ OPT(AST_SymbolRef, function(self, compressor){
@@ -3436,11 +3417,11 @@ merge(Compressor.prototype, {
&& (!self.scope.uses_with || !compressor.find_parent(AST_With))) { && (!self.scope.uses_with || !compressor.find_parent(AST_With))) {
switch (self.name) { switch (self.name) {
case "undefined": case "undefined":
return make_node(AST_Undefined, self).transform(compressor); return make_node(AST_Undefined, self).optimize(compressor);
case "NaN": case "NaN":
return make_node(AST_NaN, self).transform(compressor); return make_node(AST_NaN, self).optimize(compressor);
case "Infinity": case "Infinity":
return make_node(AST_Infinity, self).transform(compressor); return make_node(AST_Infinity, self).optimize(compressor);
} }
} }
if (compressor.option("evaluate") && compressor.option("reduce_vars")) { if (compressor.option("evaluate") && compressor.option("reduce_vars")) {
@@ -3448,18 +3429,20 @@ merge(Compressor.prototype, {
if (d.fixed) { if (d.fixed) {
if (d.should_replace === undefined) { if (d.should_replace === undefined) {
var init = d.fixed.evaluate(compressor); var init = d.fixed.evaluate(compressor);
if (init.length > 1) { if (init !== d.fixed) {
var value = init[0].print_to_string().length; init = make_node_from_constant(init, d.fixed).optimize(compressor);
init = best_of_expression(init, d.fixed);
var value = init.print_to_string().length;
var name = d.name.length; var name = d.name.length;
var freq = d.references.length; var freq = d.references.length;
var overhead = d.global || !freq ? 0 : (name + 2 + value) / freq; var overhead = d.global || !freq ? 0 : (name + 2 + value) / freq;
d.should_replace = value <= name + overhead ? init[0] : false; d.should_replace = value <= name + overhead ? init : false;
} else { } else {
d.should_replace = false; d.should_replace = false;
} }
} }
if (d.should_replace) { if (d.should_replace) {
return d.should_replace; return d.should_replace.clone(true);
} }
} }
} }
@@ -3524,8 +3507,8 @@ merge(Compressor.prototype, {
return AST_Seq.cons(car, self); return AST_Seq.cons(car, self);
} }
var cond = self.condition.evaluate(compressor); var cond = self.condition.evaluate(compressor);
if (cond.length > 1) { if (cond !== self.condition) {
if (cond[1]) { if (cond) {
compressor.warn("Condition always true [{file}:{line},{col}]", self.start); compressor.warn("Condition always true [{file}:{line},{col}]", self.start);
return maintain_this_binding(compressor.parent(), self, self.consequent); return maintain_this_binding(compressor.parent(), self, self.consequent);
} else { } else {
@@ -3533,9 +3516,8 @@ merge(Compressor.prototype, {
return maintain_this_binding(compressor.parent(), self, self.alternative); return maintain_this_binding(compressor.parent(), self, self.alternative);
} }
} }
var statement = first_in_statement(compressor); var negated = cond.negate(compressor, first_in_statement(compressor));
var negated = cond[0].negate(compressor, statement); if (best_of(compressor, cond, negated) === negated) {
if ((statement ? best_of_statement : best_of)(cond[0], negated) === negated) {
self = make_node(AST_Conditional, self, { self = make_node(AST_Conditional, self, {
condition: negated, condition: negated,
consequent: self.alternative, consequent: self.alternative,
@@ -3715,7 +3697,12 @@ merge(Compressor.prototype, {
}); });
} }
} }
return self.evaluate(compressor)[0]; var ev = self.evaluate(compressor);
if (ev !== self) {
ev = make_node_from_constant(ev, self).optimize(compressor);
return best_of(compressor, ev, self);
}
return self;
}); });
OPT(AST_Dot, function(self, compressor){ OPT(AST_Dot, function(self, compressor){
@@ -3754,13 +3741,17 @@ merge(Compressor.prototype, {
break; break;
} }
} }
return self.evaluate(compressor)[0]; var ev = self.evaluate(compressor);
if (ev !== self) {
ev = make_node_from_constant(ev, self).optimize(compressor);
return best_of(compressor, ev, self);
}
return self;
}); });
function literals_in_boolean_context(self, compressor) { function literals_in_boolean_context(self, compressor) {
if (compressor.option("booleans") && compressor.in_boolean_context()) { if (compressor.option("booleans") && compressor.in_boolean_context()) {
var best = first_in_statement(compressor) ? best_of_statement : best_of; return best_of(compressor, self, make_node(AST_Seq, self, {
return best(self, make_node(AST_Seq, self, {
car: self, car: self,
cdr: make_node(AST_True, self) cdr: make_node(AST_True, self)
}).optimize(compressor)); }).optimize(compressor));

View File

@@ -126,9 +126,11 @@ function merge(obj, ext) {
return count; return count;
}; };
function noop() {}; function noop() {}
function return_false() { return false; } function return_false() { return false; }
function return_true() { return true; } function return_true() { return true; }
function return_this() { return this; }
function return_null() { return null; }
var MAP = (function(){ var MAP = (function(){
function MAP(a, f, backwards) { function MAP(a, f, backwards) {

View File

@@ -4,7 +4,7 @@
"homepage": "http://lisperator.net/uglifyjs", "homepage": "http://lisperator.net/uglifyjs",
"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": "2.8.12", "version": "2.8.14",
"engines": { "engines": {
"node": ">=0.8.0" "node": ">=0.8.0"
}, },

View File

@@ -43,6 +43,7 @@ collapse_vars_side_effects_1: {
z = i += 4; z = i += 4;
log(x, z, y, i); log(x, z, y, i);
} }
f1(), f2(), f3(), f4();
} }
expect: { expect: {
function f1() { function f1() {
@@ -73,7 +74,9 @@ collapse_vars_side_effects_1: {
y = i += 3; y = i += 3;
log(x, i += 4, y, i); log(x, i += 4, y, i);
} }
f1(), f2(), f3(), f4();
} }
expect_stdout: true
} }
collapse_vars_side_effects_2: { collapse_vars_side_effects_2: {
@@ -823,6 +826,7 @@ collapse_vars_repeated: {
console.log(e + "!"); console.log(e + "!");
})("!"); })("!");
} }
expect_stdout: true
} }
collapse_vars_closures: { collapse_vars_closures: {
@@ -1109,6 +1113,7 @@ collapse_vars_eval_and_with: {
return function() { with (o) console.log(a) }; return function() { with (o) console.log(a) };
})()(); })()();
} }
expect_stdout: true
} }
collapse_vars_constants: { collapse_vars_constants: {
@@ -1152,7 +1157,8 @@ collapse_vars_arguments: {
options = { options = {
collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true,
comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true,
keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true,
toplevel:true
} }
input: { input: {
var outer = function() { var outer = function() {
@@ -1167,6 +1173,7 @@ collapse_vars_arguments: {
(function(){console.log(arguments);})(7, 1); (function(){console.log(arguments);})(7, 1);
})(); })();
} }
expect_stdout: true
} }
collapse_vars_short_circuit: { collapse_vars_short_circuit: {
@@ -1316,6 +1323,7 @@ collapse_vars_regexp: {
console.log(result[0]); console.log(result[0]);
})(); })();
} }
expect_stdout: true
} }
issue_1537: { issue_1537: {
@@ -1335,6 +1343,7 @@ issue_1537: {
issue_1562: { issue_1562: {
options = { options = {
collapse_vars: true, collapse_vars: true,
toplevel: true,
} }
input: { input: {
var v = 1, B = 2; var v = 1, B = 2;
@@ -1363,3 +1372,46 @@ issue_1562: {
for (; f(z + 2) ;) bar(30); for (; f(z + 2) ;) bar(30);
} }
} }
issue_1605_1: {
options = {
collapse_vars: true,
toplevel: false,
}
input: {
function foo(x) {
var y = x;
return y;
}
var o = new Object;
o.p = 1;
}
expect: {
function foo(x) {
return x;
}
var o = new Object;
o.p = 1;
}
}
issue_1605_2: {
options = {
collapse_vars: true,
toplevel: "vars",
}
input: {
function foo(x) {
var y = x;
return y;
}
var o = new Object;
o.p = 1;
}
expect: {
function foo(x) {
return x;
}
(new Object).p = 1;
}
}

View File

@@ -51,6 +51,7 @@ concat_2: {
"1" + "2" + "3" "1" + "2" + "3"
); );
} }
expect_stdout: true
} }
concat_3: { concat_3: {
@@ -79,6 +80,7 @@ concat_3: {
1 + 2 + "3" + "4" + "5" 1 + 2 + "3" + "4" + "5"
); );
} }
expect_stdout: true
} }
concat_4: { concat_4: {
@@ -107,6 +109,7 @@ concat_4: {
1 + "2" + "3" + "4" + "5" 1 + "2" + "3" + "4" + "5"
); );
} }
expect_stdout: true
} }
concat_5: { concat_5: {
@@ -135,6 +138,7 @@ concat_5: {
"1" + 2 + "3" + "4" + "5" "1" + 2 + "3" + "4" + "5"
); );
} }
expect_stdout: true
} }
concat_6: { concat_6: {
@@ -163,6 +167,7 @@ concat_6: {
"1" + "2" + "3" + "4" + "5" "1" + "2" + "3" + "4" + "5"
); );
} }
expect_stdout: true
} }
concat_7: { concat_7: {
@@ -188,6 +193,7 @@ concat_7: {
x += "foo" x += "foo"
); );
} }
expect_stdout: true
} }
concat_8: { concat_8: {
@@ -213,4 +219,5 @@ concat_8: {
x += "foo" x += "foo"
); );
} }
expect_stdout: true
} }

View File

@@ -797,3 +797,99 @@ no_evaluate: {
} }
} }
} }
equality_conditionals_false: {
options = {
conditionals: false,
sequences: true,
}
input: {
function f(a, b, c) {
console.log(
a == (b ? a : a),
a == (b ? a : c),
a != (b ? a : a),
a != (b ? a : c),
a === (b ? a : a),
a === (b ? a : c),
a !== (b ? a : a),
a !== (b ? a : c)
);
}
f(0, 0, 0);
f(0, true, 0);
f(1, 2, 3);
f(1, null, 3);
f(NaN);
f(NaN, "foo");
}
expect: {
function f(a, b, c) {
console.log(
a == (b ? a : a),
a == (b ? a : c),
a != (b ? a : a),
a != (b ? a : c),
a === (b ? a : a),
a === (b ? a : c),
a !== (b ? a : a),
a !== (b ? a : c)
);
}
f(0, 0, 0),
f(0, true, 0),
f(1, 2, 3),
f(1, null, 3),
f(NaN),
f(NaN, "foo");
}
expect_stdout: true
}
equality_conditionals_true: {
options = {
conditionals: true,
sequences: true,
}
input: {
function f(a, b, c) {
console.log(
a == (b ? a : a),
a == (b ? a : c),
a != (b ? a : a),
a != (b ? a : c),
a === (b ? a : a),
a === (b ? a : c),
a !== (b ? a : a),
a !== (b ? a : c)
);
}
f(0, 0, 0);
f(0, true, 0);
f(1, 2, 3);
f(1, null, 3);
f(NaN);
f(NaN, "foo");
}
expect: {
function f(a, b, c) {
console.log(
(b, a == a),
a == (b ? a : c),
(b, a != a),
a != (b ? a : c),
(b, a === a),
a === (b ? a : c),
(b, a !== a),
a !== (b ? a : c)
);
}
f(0, 0, 0),
f(0, true, 0),
f(1, 2, 3),
f(1, null, 3),
f(NaN),
f(NaN, "foo");
}
expect_stdout: true
}

View File

@@ -162,4 +162,5 @@ regexp_literal_not_const: {
while (result = REGEXP_LITERAL.exec("acdabcdeabbb")) console.log(result[0]); while (result = REGEXP_LITERAL.exec("acdabcdeabbb")) console.log(result[0]);
})(); })();
} }
expect_stdout: true
} }

View File

@@ -87,6 +87,7 @@ dead_code_constant_boolean_should_warn_more: {
var x = 10, y; var x = 10, y;
var moo; var moo;
} }
expect_stdout: true
} }
dead_code_const_declaration: { dead_code_const_declaration: {
@@ -113,6 +114,7 @@ dead_code_const_declaration: {
var moo; var moo;
function bar() {} function bar() {}
} }
expect_stdout: true
} }
dead_code_const_annotation: { dead_code_const_annotation: {
@@ -140,6 +142,7 @@ dead_code_const_annotation: {
var moo; var moo;
function bar() {} function bar() {}
} }
expect_stdout: true
} }
dead_code_const_annotation_regex: { dead_code_const_annotation_regex: {
@@ -163,6 +166,7 @@ dead_code_const_annotation_regex: {
var CONST_FOO_ANN = !1; var CONST_FOO_ANN = !1;
CONST_FOO_ANN && console.log('reachable'); CONST_FOO_ANN && console.log('reachable');
} }
expect_stdout: true
} }
dead_code_const_annotation_complex_scope: { dead_code_const_annotation_complex_scope: {
@@ -208,4 +212,5 @@ dead_code_const_annotation_complex_scope: {
var meat = 'beef'; var meat = 'beef';
var pork = 'bad'; var pork = 'bad';
} }
expect_stdout: true
} }

View File

@@ -200,6 +200,7 @@ negative_zero: {
1 / (-0) 1 / (-0)
); );
} }
expect_stdout: true
} }
positive_zero: { positive_zero: {
@@ -220,6 +221,7 @@ positive_zero: {
1 / (0) 1 / (0)
); );
} }
expect_stdout: true
} }
unsafe_constant: { unsafe_constant: {
@@ -243,6 +245,7 @@ unsafe_constant: {
(void 0).a (void 0).a
); );
} }
expect_stdout: true
} }
unsafe_object: { unsafe_object: {
@@ -266,6 +269,7 @@ unsafe_object: {
1..b + 1 1..b + 1
); );
} }
expect_stdout: true
} }
unsafe_object_nested: { unsafe_object_nested: {
@@ -289,6 +293,7 @@ unsafe_object_nested: {
2 2
); );
} }
expect_stdout: true
} }
unsafe_object_complex: { unsafe_object_complex: {
@@ -312,6 +317,7 @@ unsafe_object_complex: {
2 2
); );
} }
expect_stdout: true
} }
unsafe_object_repeated: { unsafe_object_repeated: {
@@ -335,6 +341,7 @@ unsafe_object_repeated: {
1..b + 1 1..b + 1
); );
} }
expect_stdout: true
} }
unsafe_object_accessor: { unsafe_object_accessor: {
@@ -384,6 +391,7 @@ unsafe_function: {
({a:{b:1},b:function(){}}).a.b + 1 ({a:{b:1},b:function(){}}).a.b + 1
); );
} }
expect_stdout: true
} }
unsafe_integer_key: { unsafe_integer_key: {
@@ -411,6 +419,7 @@ unsafe_integer_key: {
1["1"] + 1 1["1"] + 1
); );
} }
expect_stdout: true
} }
unsafe_integer_key_complex: { unsafe_integer_key_complex: {
@@ -438,6 +447,7 @@ unsafe_integer_key_complex: {
2 2
); );
} }
expect_stdout: true
} }
unsafe_float_key: { unsafe_float_key: {
@@ -465,6 +475,7 @@ unsafe_float_key: {
1["3.14"] + 1 1["3.14"] + 1
); );
} }
expect_stdout: true
} }
unsafe_float_key_complex: { unsafe_float_key_complex: {
@@ -492,6 +503,7 @@ unsafe_float_key_complex: {
2 2
); );
} }
expect_stdout: true
} }
unsafe_array: { unsafe_array: {
@@ -527,6 +539,7 @@ unsafe_array: {
(void 0)[1] + 1 (void 0)[1] + 1
); );
} }
expect_stdout: true
} }
unsafe_string: { unsafe_string: {
@@ -554,6 +567,7 @@ unsafe_string: {
"11" "11"
); );
} }
expect_stdout: true
} }
unsafe_array_bad_index: { unsafe_array_bad_index: {
@@ -575,6 +589,7 @@ unsafe_array_bad_index: {
[1, 2, 3, 4][3.14] + 1 [1, 2, 3, 4][3.14] + 1
); );
} }
expect_stdout: true
} }
unsafe_string_bad_index: { unsafe_string_bad_index: {
@@ -596,6 +611,7 @@ unsafe_string_bad_index: {
"1234"[3.14] + 1 "1234"[3.14] + 1
); );
} }
expect_stdout: true
} }
unsafe_prototype_function: { unsafe_prototype_function: {
@@ -642,6 +658,7 @@ call_args: {
console.log(1); console.log(1);
+(1, 1); +(1, 1);
} }
expect_stdout: true
} }
call_args_drop_param: { call_args_drop_param: {
@@ -663,6 +680,7 @@ call_args_drop_param: {
console.log(1); console.log(1);
+(b, 1); +(b, 1);
} }
expect_stdout: true
} }
in_boolean_context: { in_boolean_context: {
@@ -700,4 +718,74 @@ in_boolean_context: {
(foo(), !1) (foo(), !1)
); );
} }
expect_stdout: true
}
unsafe_charAt: {
options = {
evaluate : true,
unsafe : true
}
input: {
console.log(
"1234" + 1,
"1234".charAt(0) + 1,
"1234".charAt(6 - 5) + 1,
("12" + "34").charAt(0) + 1,
("12" + "34").charAt(6 - 5) + 1,
[1, 2, 3, 4].join("").charAt(0) + 1
);
}
expect: {
console.log(
"12341",
"11",
"21",
"11",
"21",
"11"
);
}
expect_stdout: true
}
unsafe_charAt_bad_index: {
options = {
evaluate : true,
unsafe : true
}
input: {
console.log(
"1234".charAt() + 1,
"1234".charAt("a") + 1,
"1234".charAt(3.14) + 1
);
}
expect: {
console.log(
"11",
"11",
"41"
);
}
expect_stdout: true
}
unsafe_charAt_noop: {
options = {
evaluate : true,
unsafe : true
}
input: {
console.log(
s.charAt(0),
"string".charAt(x)
);
}
expect: {
console.log(
s.charAt(0),
"string".charAt(x)
);
}
} }

View File

@@ -39,6 +39,7 @@ iifes_returning_constants_keep_fargs_true: {
console.log(6); console.log(6);
console.log((a(), b(), 6)); console.log((a(), b(), 6));
} }
expect_stdout: true
} }
iifes_returning_constants_keep_fargs_false: { iifes_returning_constants_keep_fargs_false: {
@@ -73,6 +74,7 @@ iifes_returning_constants_keep_fargs_false: {
console.log(6); console.log(6);
console.log((a(), b(), 6)); console.log((a(), b(), 6));
} }
expect_stdout: true
} }
issue_485_crashing_1530: { issue_485_crashing_1530: {

View File

@@ -49,4 +49,3 @@ mangle_keep_fnames_true: {
} }
} }
} }

View File

@@ -46,4 +46,5 @@ string_plus_optimization: {
} }
foo(); foo();
} }
expect_stdout: true
} }

View File

@@ -14,6 +14,7 @@ issue_1321_no_debug: {
x["a"] = 2 * x.b; x["a"] = 2 * x.b;
console.log(x.b, x["a"]); console.log(x.b, x["a"]);
} }
expect_stdout: true
} }
issue_1321_debug: { issue_1321_debug: {
@@ -33,6 +34,7 @@ issue_1321_debug: {
x["_$foo$_"] = 2 * x.a; x["_$foo$_"] = 2 * x.a;
console.log(x.a, x["_$foo$_"]); console.log(x.a, x["_$foo$_"]);
} }
expect_stdout: true
} }
issue_1321_with_quoted: { issue_1321_with_quoted: {
@@ -51,4 +53,5 @@ issue_1321_with_quoted: {
x["b"] = 2 * x.a; x["b"] = 2 * x.a;
console.log(x.a, x["b"]); console.log(x.a, x["b"]);
} }
expect_stdout: true
} }

View File

@@ -42,4 +42,5 @@ conditional_false_stray_else_in_loop: {
} }
} }
expect_exact: "for(var i=1;i<=4;++i)if(!(i<=2))console.log(i);" expect_exact: "for(var i=1;i<=4;++i)if(!(i<=2))console.log(i);"
expect_stdout: true
} }

View File

@@ -0,0 +1,99 @@
screw_ie8: {
options = {
screw_ie8: true,
}
mangle = {
screw_ie8: true,
}
input: {
try { throw "foo"; } catch (x) { console.log(x); }
}
expect_exact: 'try{throw"foo"}catch(o){console.log(o)}'
expect_stdout: [
"foo"
]
}
support_ie8: {
options = {
screw_ie8: false,
}
mangle = {
screw_ie8: false,
}
input: {
try { throw "foo"; } catch (x) { console.log(x); }
}
expect_exact: 'try{throw"foo"}catch(x){console.log(x)}'
expect_stdout: "foo"
}
safe_undefined: {
options = {
conditionals: true,
if_return: true,
unsafe: false,
}
mangle = {}
input: {
var a, c;
console.log(function(undefined) {
return function() {
if (a)
return b;
if (c)
return d;
};
}(1)());
}
expect: {
var a, c;
console.log(function(n) {
return function() {
return a ? b : c ? d : void 0;
};
}(1)());
}
expect_stdout: true
}
unsafe_undefined: {
options = {
conditionals: true,
if_return: true,
unsafe: true,
}
mangle = {}
input: {
var a, c;
console.log(function(undefined) {
return function() {
if (a)
return b;
if (c)
return d;
};
}()());
}
expect: {
var a, c;
console.log(function(n) {
return function() {
return a ? b : c ? d : n;
};
}()());
}
expect_stdout: true
}
runtime_error: {
input: {
const a = 1;
console.log(a++);
}
expect: {
const a = 1;
console.log(a++);
}
expect_stdout: true
}

View File

@@ -0,0 +1,56 @@
chained_evaluation_1: {
options = {
collapse_vars: true,
evaluate: true,
reduce_vars: true,
unused: true,
}
input: {
(function() {
var a = 1;
(function() {
var b = a, c;
c = f(b);
c.bar = b;
})();
})();
}
expect: {
(function() {
(function() {
var c;
c = f(1);
c.bar = 1;
})();
})();
}
}
chained_evaluation_2: {
options = {
collapse_vars: true,
evaluate: true,
reduce_vars: true,
unused: true,
}
input: {
(function() {
var a = "long piece of string";
(function() {
var b = a, c;
c = f(b);
c.bar = b;
})();
})();
}
expect: {
(function() {
var a = "long piece of string";
(function() {
var c;
c = f(a);
c.bar = a;
})();
})();
}
}

View File

@@ -48,6 +48,7 @@ dead_code_const_annotation_regex: {
var CONST_FOO_ANN = !1; var CONST_FOO_ANN = !1;
if (CONST_FOO_ANN) console.log('reachable'); if (CONST_FOO_ANN) console.log('reachable');
} }
expect_stdout: true
} }
drop_console_2: { drop_console_2: {
@@ -225,6 +226,7 @@ issue_1254_negate_iife_true: {
})()(); })()();
} }
expect_exact: '(function(){return function(){console.log("test")}})()();' expect_exact: '(function(){return function(){console.log("test")}})()();'
expect_stdout: true
} }
issue_1254_negate_iife_nested: { issue_1254_negate_iife_nested: {
@@ -240,6 +242,7 @@ issue_1254_negate_iife_nested: {
})()()()()(); })()()()()();
} }
expect_exact: '(function(){return function(){console.log("test")}})()()()()();' expect_exact: '(function(){return function(){console.log("test")}})()()()()();'
expect_stdout: true
} }
conditional: { conditional: {

View File

@@ -29,4 +29,5 @@ dont_mangle_arguments: {
})(5,6,7); })(5,6,7);
} }
expect_exact: "(function(){var arguments=arguments,o=9;console.log(o,arguments)})(5,6,7);" expect_exact: "(function(){var arguments=arguments,o=9;console.log(o,arguments)})(5,6,7);"
expect_stdout: true
} }

View File

@@ -50,6 +50,7 @@ this_binding_conditionals: {
this_binding_collapse_vars: { this_binding_collapse_vars: {
options = { options = {
collapse_vars: true, collapse_vars: true,
toplevel: true,
}; };
input: { input: {
var c = a; c(); var c = a; c();

View File

@@ -42,6 +42,7 @@ eval_collapse_vars: {
eval("console.log(a);"); eval("console.log(a);");
})(eval); })(eval);
} }
expect_stdout: true
} }
eval_unused: { eval_unused: {

View File

@@ -9,6 +9,7 @@ labels_1: {
expect: { expect: {
foo || console.log("bar"); foo || console.log("bar");
} }
expect_stdout: true
} }
labels_2: { labels_2: {
@@ -40,6 +41,7 @@ labels_3: {
for (var i = 0; i < 5; ++i) for (var i = 0; i < 5; ++i)
i < 3 || console.log(i); i < 3 || console.log(i);
} }
expect_stdout: true
} }
labels_4: { labels_4: {
@@ -54,6 +56,7 @@ labels_4: {
for (var i = 0; i < 5; ++i) for (var i = 0; i < 5; ++i)
i < 3 || console.log(i); i < 3 || console.log(i);
} }
expect_stdout: true
} }
labels_5: { labels_5: {

View File

@@ -166,6 +166,7 @@ keep_collapse_const_in_own_block_scope: {
console.log(i); console.log(i);
console.log(c); console.log(c);
} }
expect_stdout: true
} }
keep_collapse_const_in_own_block_scope_2: { keep_collapse_const_in_own_block_scope_2: {
@@ -186,6 +187,7 @@ keep_collapse_const_in_own_block_scope_2: {
console.log(i); console.log(i);
console.log(c); console.log(c);
} }
expect_stdout: true
} }
evaluate: { evaluate: {

View File

@@ -70,6 +70,7 @@ negate_iife_3_evaluate: {
expect: { expect: {
console.log(true); console.log(true);
} }
expect_stdout: true
} }
negate_iife_3_side_effects: { negate_iife_3_side_effects: {
@@ -111,6 +112,7 @@ negate_iife_3_off_evaluate: {
expect: { expect: {
console.log(true); console.log(true);
} }
expect_stdout: true
} }
negate_iife_4: { negate_iife_4: {
@@ -243,6 +245,7 @@ negate_iife_nested: {
}(7); }(7);
}).f(); }).f();
} }
expect_stdout: true
} }
negate_iife_nested_off: { negate_iife_nested_off: {
@@ -275,6 +278,7 @@ negate_iife_nested_off: {
})(7); })(7);
}).f(); }).f();
} }
expect_stdout: true
} }
negate_iife_issue_1073: { negate_iife_issue_1073: {
@@ -299,6 +303,7 @@ negate_iife_issue_1073: {
}; };
}(7))(); }(7))();
} }
expect_stdout: true
} }
issue_1254_negate_iife_false: { issue_1254_negate_iife_false: {
@@ -313,6 +318,7 @@ issue_1254_negate_iife_false: {
})()(); })()();
} }
expect_exact: '(function(){return function(){console.log("test")}})()();' expect_exact: '(function(){return function(){console.log("test")}})()();'
expect_stdout: true
} }
issue_1254_negate_iife_true: { issue_1254_negate_iife_true: {
@@ -327,6 +333,7 @@ issue_1254_negate_iife_true: {
})()(); })()();
} }
expect_exact: '!function(){return function(){console.log("test")}}()();' expect_exact: '!function(){return function(){console.log("test")}}()();'
expect_stdout: true
} }
issue_1254_negate_iife_nested: { issue_1254_negate_iife_nested: {
@@ -341,6 +348,7 @@ issue_1254_negate_iife_nested: {
})()()()()(); })()()()()();
} }
expect_exact: '!function(){return function(){console.log("test")}}()()()()();' expect_exact: '!function(){return function(){console.log("test")}}()()()()();'
expect_stdout: true
} }
issue_1288: { issue_1288: {

View File

@@ -58,6 +58,7 @@ reduce_vars: {
})(); })();
console.log(2); console.log(2);
} }
expect_stdout: true
} }
modified: { modified: {
@@ -166,7 +167,7 @@ modified: {
console.log(A ? 'yes' : 'no'); console.log(A ? 'yes' : 'no');
console.log(B ? 'yes' : 'no'); console.log(B ? 'yes' : 'no');
} }
} }
} }
unsafe_evaluate: { unsafe_evaluate: {
@@ -401,6 +402,7 @@ iife: {
console.log(0, 1 * b, 5); console.log(0, 1 * b, 5);
}(1, 2, 3); }(1, 2, 3);
} }
expect_stdout: true
} }
iife_new: { iife_new: {
@@ -420,6 +422,7 @@ iife_new: {
console.log(0, 1 * b, 5); console.log(0, 1 * b, 5);
}(1, 2, 3); }(1, 2, 3);
} }
expect_stdout: true
} }
multi_def: { multi_def: {
@@ -707,6 +710,7 @@ toplevel_on: {
expect: { expect: {
console.log(3); console.log(3);
} }
expect_stdout: true
} }
toplevel_off: { toplevel_off: {
@@ -724,6 +728,7 @@ toplevel_off: {
var x = 3; var x = 3;
console.log(x); console.log(x);
} }
expect_stdout: true
} }
toplevel_on_loops_1: { toplevel_on_loops_1: {
@@ -751,6 +756,7 @@ toplevel_on_loops_1: {
})(); })();
while (x); while (x);
} }
expect_stdout: true
} }
toplevel_off_loops_1: { toplevel_off_loops_1: {
@@ -779,6 +785,7 @@ toplevel_off_loops_1: {
bar(); bar();
while (x); while (x);
} }
expect_stdout: true
} }
toplevel_on_loops_2: { toplevel_on_loops_2: {
@@ -1121,6 +1128,7 @@ defun_label: {
}(2)); }(2));
}(); }();
} }
expect_stdout: true
} }
double_reference: { double_reference: {
@@ -1164,6 +1172,7 @@ iife_arguments_1: {
return f; return f;
}); });
} }
expect_stdout: true
} }
iife_arguments_2: { iife_arguments_2: {
@@ -1186,6 +1195,7 @@ iife_arguments_2: {
}() === arguments[0]); }() === arguments[0]);
})(); })();
} }
expect_stdout: true
} }
iife_eval_1: { iife_eval_1: {
@@ -1207,6 +1217,7 @@ iife_eval_1: {
return f; return f;
}); });
} }
expect_stdout: true
} }
iife_eval_2: { iife_eval_2: {
@@ -1230,6 +1241,7 @@ iife_eval_2: {
console.log(x() === eval("x")); console.log(x() === eval("x"));
})(); })();
} }
expect_stdout: true
} }
iife_func_side_effects: { iife_func_side_effects: {
@@ -1326,4 +1338,29 @@ issue_1595_4: {
if (a) iife(a - 1, b, c); if (a) iife(a - 1, b, c);
})(3, 4, 5); })(3, 4, 5);
} }
expect_stdout: true
}
issue_1606: {
options = {
evaluate: true,
hoist_vars: true,
reduce_vars: true,
}
input: {
function f() {
var a;
function g(){};
var b = 2;
x(b);
}
}
expect: {
function f() {
var a, b;
function g(){};
b = 2;
x(b);
}
}
} }

View File

@@ -1,20 +1,29 @@
do_screw: { do_screw: {
options = { screw_ie8: true }; options = {
screw_ie8: true,
}
beautify = { beautify = {
screw_ie8: true, screw_ie8: true,
ascii_only: true ascii_only: true,
}; }
input: {
input: f("\v"); f("\v");
expect_exact: 'f("\\v");'; }
expect_exact: 'f("\\v");'
} }
dont_screw: { dont_screw: {
options = { screw_ie8: false }; options = {
beautify = { screw_ie8: false, ascii_only: true }; screw_ie8: false,
}
input: f("\v"); beautify = {
expect_exact: 'f("\\x0B");'; screw_ie8: false,
ascii_only: true,
}
input: {
f("\v");
}
expect_exact: 'f("\\x0B");'
} }
do_screw_constants: { do_screw_constants: {
@@ -119,6 +128,7 @@ do_screw_try_catch_undefined: {
return void 0===o return void 0===o
} }
} }
expect_stdout: true
} }
dont_screw_try_catch_undefined: { dont_screw_try_catch_undefined: {
@@ -147,6 +157,7 @@ dont_screw_try_catch_undefined: {
return n === undefined return n === undefined
} }
} }
expect_stdout: true
} }
reduce_vars: { reduce_vars: {
@@ -199,6 +210,7 @@ issue_1586_1: {
} }
} }
expect_exact: "function f(){try{}catch(c){console.log(c.message)}}" expect_exact: "function f(){try{}catch(c){console.log(c.message)}}"
expect_stdout: true
} }
issue_1586_2: { issue_1586_2: {
@@ -217,4 +229,5 @@ issue_1586_2: {
} }
} }
expect_exact: "function f(){try{}catch(c){console.log(c.message)}}" expect_exact: "function f(){try{}catch(c){console.log(c.message)}}"
expect_stdout: true
} }

View File

@@ -86,6 +86,7 @@ make_sequences_4: {
switch (x = 5, y) {} switch (x = 5, y) {}
with (x = 5, obj); with (x = 5, obj);
} }
expect_stdout: true
} }
lift_sequences_1: { lift_sequences_1: {
@@ -103,15 +104,18 @@ lift_sequences_1: {
lift_sequences_2: { lift_sequences_2: {
options = { sequences: true, evaluate: true }; options = { sequences: true, evaluate: true };
input: { input: {
var foo, bar; var foo = 1, bar;
foo.x = (foo = {}, 10); foo.x = (foo = {}, 10);
bar = (bar = {}, 10); bar = (bar = {}, 10);
console.log(foo, bar);
} }
expect: { expect: {
var foo, bar; var foo = 1, bar;
foo.x = (foo = {}, 10), foo.x = (foo = {}, 10),
bar = {}, bar = 10; bar = {}, bar = 10,
console.log(foo, bar);
} }
expect_stdout: true
} }
lift_sequences_3: { lift_sequences_3: {
@@ -138,6 +142,23 @@ lift_sequences_4: {
} }
} }
lift_sequences_5: {
options = {
sequences: true,
}
input: {
var a = 2, b;
a *= (b, a = 4, 3);
console.log(a);
}
expect: {
var a = 2, b;
b, a *= (a = 4, 3),
console.log(a);
}
expect_stdout: "6"
}
for_sequences: { for_sequences: {
options = { sequences: true }; options = { sequences: true };
input: { input: {
@@ -230,6 +251,7 @@ negate_iife_for: {
for (!function() {}(), i = 0; i < 5; i++) console.log(i); for (!function() {}(), i = 0; i < 5; i++) console.log(i);
for (function() {}(); i < 5; i++) console.log(i); for (function() {}(); i < 5; i++) console.log(i);
} }
expect_stdout: true
} }
iife: { iife: {

131
test/compress/transform.js Normal file
View File

@@ -0,0 +1,131 @@
booleans_evaluate: {
options = {
booleans: true,
evaluate: true,
}
input: {
console.log(typeof void 0 != "undefined");
console.log(1 == 1, 1 === 1)
console.log(1 != 1, 1 !== 1)
}
expect: {
console.log(!1);
console.log(!0, !0);
console.log(!1, !1);
}
expect_stdout: true
}
booleans_global_defs: {
options = {
booleans: true,
evaluate: true,
global_defs: {
A: true,
},
}
input: {
console.log(A == 1);
}
expect: {
console.log(!0);
}
expect_stdout: true
}
condition_evaluate: {
options = {
booleans: true,
dead_code: false,
evaluate: true,
loops: false,
}
input: {
while (1 === 2);
for (; 1 == true;);
if (void 0 == null);
}
expect: {
while (!1);
for (; !0;);
if (!0);
}
}
if_else_empty: {
options = {
conditionals: true,
}
input: {
if ({} ? a : b); else {}
}
expect: {
!{} ? b : a;
}
}
label_if_break: {
options = {
conditionals: true,
dead_code: true,
evaluate: true,
}
input: {
L: if (true) {
a;
break L;
}
}
expect: {
a;
}
}
while_if_break: {
options = {
conditionals: true,
loops: true,
sequences: true,
}
input: {
while (a) {
if (b) if(c) d;
if (e) break;
}
}
expect: {
for(; a && (b && c && d, !e););
}
}
if_return: {
options = {
booleans: true,
conditionals: true,
if_return: true,
sequences: true,
}
input: {
function f(w, x, y, z) {
if (x) return;
if (w) {
if (y) return;
} else if (z) return;
if (x == y) return true;
if (x) w();
if (y) z();
return true;
}
}
expect: {
function f(w, x, y, z) {
if (!x) {
if (w) {
if (y) return;
} else if (z) return;
return x == y || (x && w(), y && z(), !0);
}
}
}
}

View File

@@ -152,7 +152,7 @@ describe("bin/uglifyjs", function () {
}); });
}); });
it("Should process inline source map", function(done) { it("Should process inline source map", function(done) {
var command = uglifyjscmd + ' test/input/issue-520/input.js -cm toplevel --in-source-map inline --source-map-inline'; var command = uglifyjscmd + ' test/input/issue-520/input.js -mc toplevel --in-source-map inline --source-map-inline';
exec(command, function (err, stdout) { exec(command, function (err, stdout) {
if (err) throw err; if (err) throw err;

View File

@@ -78,6 +78,7 @@ describe("minify", function() {
}); });
it("Should process inline source map", function() { it("Should process inline source map", function() {
var code = Uglify.minify("./test/input/issue-520/input.js", { var code = Uglify.minify("./test/input/issue-520/input.js", {
compress: { toplevel: true },
inSourceMap: "inline", inSourceMap: "inline",
sourceMapInline: true sourceMapInline: true
}).code + "\n"; }).code + "\n";

View File

@@ -6,10 +6,22 @@ var U = require("../tools/node");
var path = require("path"); var path = require("path");
var fs = require("fs"); var fs = require("fs");
var assert = require("assert"); var assert = require("assert");
var vm = require("vm");
var tests_dir = path.dirname(module.filename); var tests_dir = path.dirname(module.filename);
var failures = 0; var failures = 0;
var failed_files = {}; var failed_files = {};
var same_stdout = ~process.version.lastIndexOf("v0.12.", 0) ? function(expected, actual) {
if (typeof expected != typeof actual) return false;
if (typeof expected != "string") {
if (expected.name != actual.name) return false;
expected = expected.message.slice(expected.message.lastIndexOf("\n") + 1);
actual = actual.message.slice(actual.message.lastIndexOf("\n") + 1);
}
return expected == actual;
} : function(expected, actual) {
return typeof expected == typeof actual && expected.toString() == actual.toString();
};
run_compress_tests(); run_compress_tests();
if (failures) { if (failures) {
@@ -71,10 +83,15 @@ function test_directory(dir) {
} }
function as_toplevel(input, mangle_options) { function as_toplevel(input, mangle_options) {
if (input instanceof U.AST_BlockStatement) input = input.body; if (!(input instanceof U.AST_BlockStatement))
else if (input instanceof U.AST_Statement) input = [ input ]; throw new Error("Unsupported input syntax");
else throw new Error("Unsupported input syntax"); for (var i = 0; i < input.body.length; i++) {
var toplevel = new U.AST_Toplevel({ body: input }); var stat = input.body[i];
if (stat instanceof U.AST_SimpleStatement && stat.body instanceof U.AST_String)
input.body[i] = new U.AST_Directive(stat.body);
else break;
}
var toplevel = new U.AST_Toplevel(input);
toplevel.figure_out_scope(mangle_options); toplevel.figure_out_scope(mangle_options);
return toplevel; return toplevel;
} }
@@ -165,6 +182,36 @@ function run_compress_tests() {
failed_files[file] = 1; failed_files[file] = 1;
} }
} }
if (test.expect_stdout) {
var stdout = run_code(make_code(input, output_options));
if (test.expect_stdout === true) {
test.expect_stdout = stdout;
}
if (!same_stdout(test.expect_stdout, stdout)) {
log("!!! Invalid input or expected stdout\n---INPUT---\n{input}\n---EXPECTED {expected_type}---\n{expected}\n---ACTUAL {actual_type}---\n{actual}\n\n", {
input: input_code,
expected_type: typeof test.expect_stdout == "string" ? "STDOUT" : "ERROR",
expected: test.expect_stdout,
actual_type: typeof stdout == "string" ? "STDOUT" : "ERROR",
actual: stdout,
});
failures++;
failed_files[file] = 1;
} else {
stdout = run_code(output);
if (!same_stdout(test.expect_stdout, stdout)) {
log("!!! failed\n---INPUT---\n{input}\n---EXPECTED {expected_type}---\n{expected}\n---ACTUAL {actual_type}---\n{actual}\n\n", {
input: input_code,
expected_type: typeof test.expect_stdout == "string" ? "STDOUT" : "ERROR",
expected: test.expect_stdout,
actual_type: typeof stdout == "string" ? "STDOUT" : "ERROR",
actual: stdout,
});
failures++;
failed_files[file] = 1;
}
}
}
} }
} }
var tests = parse_test(path.resolve(dir, file)); var tests = parse_test(path.resolve(dir, file));
@@ -215,9 +262,9 @@ function parse_test(file) {
} }
function read_string(stat) { function read_string(stat) {
if (stat.TYPE === "SimpleStatement") { if (stat.TYPE == "SimpleStatement") {
var body = stat.body; var body = stat.body;
out: switch(body.TYPE) { switch(body.TYPE) {
case "String": case "String":
return body.value; return body.value;
case "Array": case "Array":
@@ -243,23 +290,26 @@ function parse_test(file) {
return true; return true;
} }
if (node instanceof U.AST_LabeledStatement) { if (node instanceof U.AST_LabeledStatement) {
var label = node.label;
assert.ok( assert.ok(
["input", "expect", "expect_exact", "expect_warnings"].indexOf(node.label.name) >= 0, ["input", "expect", "expect_exact", "expect_warnings", "expect_stdout"].indexOf(label.name) >= 0,
tmpl("Unsupported label {name} [{line},{col}]", { tmpl("Unsupported label {name} [{line},{col}]", {
name: node.label.name, name: label.name,
line: node.label.start.line, line: label.start.line,
col: node.label.start.col col: label.start.col
}) })
); );
var stat = node.body; var stat = node.body;
if (stat instanceof U.AST_BlockStatement) { if (label.name == "expect_exact") {
if (stat.body.length == 1) stat = stat.body[0]; test[label.name] = read_string(stat);
else if (stat.body.length == 0) stat = new U.AST_EmptyStatement(); } else if (label.name == "expect_stdout") {
} if (stat.TYPE == "SimpleStatement" && stat.body instanceof U.AST_Boolean) {
if (node.label.name === "expect_exact") { test[label.name] = stat.body.value;
test[node.label.name] = read_string(stat); } else {
test[label.name] = read_string(stat) + "\n";
}
} else { } else {
test[node.label.name] = stat; test[label.name] = stat;
} }
return true; return true;
} }
@@ -281,3 +331,19 @@ function evaluate(code) {
code = make_code(code, { beautify: true }); code = make_code(code, { beautify: true });
return new Function("return(" + code + ")")(); return new Function("return(" + code + ")")();
} }
function run_code(code) {
var stdout = "";
var original_write = process.stdout.write;
process.stdout.write = function(chunk) {
stdout += chunk;
};
try {
new vm.Script(code).runInNewContext({ console: console }, { timeout: 5000 });
return stdout;
} catch (ex) {
return ex;
} finally {
process.stdout.write = original_write;
}
}