Compare commits

...

17 Commits

Author SHA1 Message Date
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
Alex Lam S.L
919d5e3482 v2.8.12 2017-03-11 05:00:55 +08:00
Alex Lam S.L
e3a3db73ae temporary fix for boolean bug (#1597)
fixes #1592
2017-03-11 04:59:55 +08:00
Alex Lam S.L
d9344f30b8 disallow parameter substitution for named IIFEs (#1596)
Self-referenced function has non-fixed values assigned to its parameters.

Let `unused` & `!keep_fnames` do the scanning, then apply `reduce_vars` only to unnamed functions.

fixes #1595
2017-03-11 03:34:55 +08:00
Alex Lam S.L
be80f7e706 support multi-line string in tests (#1590)
`expect_exact` sometimes have multiple lines and `\n` are hard to read.

Use array of strings to emulate line breaks and improve readability.
2017-03-10 11:27:30 +08:00
Alex Lam S.L
cf45e2f79b fixup for #1585 (#1589)
As patched on `harmony`, `statement()` is the only user of `embed_tokens()` with a missing error branch.

Updated test case and match up with `harmony` to facilitate future merging.
2017-03-10 10:49:41 +08:00
Alex Lam S.L
8354758f30 v2.8.11 2017-03-10 04:17:21 +08:00
Alex Lam S.L
9e6b128374 fix catch variable reference in IE8 (#1587)
`AST_Scope.def_variable()` will overwrite `AST_Symbol.thedef`, so save a copy before calling.

fixes #1586
2017-03-10 03:15:21 +08:00
Michael Mior
93cdb194f4 Correctly raise a parse exception with a missing loop body (#1585) 2017-03-10 03:08:43 +08:00
Alex Lam S.L
b633706ce4 fix & improve function argument compression (#1584)
- one-use function call => IIFE should take `eval()` & `arguments` into account
- if unused parameter cannot be eliminated, replace it with `0`

fixes #1583
2017-03-09 19:11:05 +08:00
20 changed files with 983 additions and 213 deletions

View File

@@ -984,8 +984,8 @@ TreeWalker.prototype = {
push: function (node) {
if (node instanceof AST_Lambda) {
this.directives = Object.create(this.directives);
} else if (node instanceof AST_Directive) {
this.directives[node.value] = this.directives[node.value] ? "up" : true;
} else if (node instanceof AST_Directive && !this.directives[node.value]) {
this.directives[node.value] = node;
}
this.stack.push(node);
},
@@ -1013,7 +1013,7 @@ TreeWalker.prototype = {
for (var i = 0; i < node.body.length; ++i) {
var st = node.body[i];
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) {
return top_retain.test(def.name);
};
} else if (typeof top_retain === "function") {
} else if (typeof top_retain == "function") {
this.top_retain = top_retain;
} else if (top_retain) {
if (typeof top_retain === "string") {
if (typeof top_retain == "string") {
top_retain = top_retain.split(/,/);
}
this.top_retain = function(def) {
@@ -151,14 +151,25 @@ merge(Compressor.prototype, {
node = node.hoist_declarations(this);
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);
node = node.optimize(this);
if (was_scope && node instanceof AST_Scope) {
node.drop_unused(this);
// Existing code relies on how AST_Node.optimize() worked, and omitting the
// following replacement call would result in degraded efficiency of both
// output and performance.
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;
return node;
if (opt === node) opt._squeezed = true;
return opt;
}
});
@@ -171,8 +182,7 @@ merge(Compressor.prototype, {
if (compressor.has_directive("use asm")) return self;
var opt = optimizer(self, compressor);
opt._optimized = true;
if (opt === self) return opt;
return opt.transform(compressor);
return opt;
});
};
@@ -279,8 +289,12 @@ merge(Compressor.prototype, {
}
var iife;
if (node instanceof AST_Function
&& !node.name
&& (iife = tw.parent()) instanceof AST_Call
&& iife.expression === node) {
// Virtually turn IIFE parameters into variable definitions:
// (function(a,b) {...})(c,d) => (function() {var a=c,b=d; ...})()
// So existing transformation rules can work on them.
node.argnames.forEach(function(arg, i) {
var d = arg.definition();
d.fixed = iife.args[i] || make_node(AST_Undefined, iife);
@@ -380,7 +394,7 @@ merge(Compressor.prototype, {
return new ctor(props);
};
function make_node_from_constant(compressor, val, orig) {
function make_node_from_constant(val, orig) {
switch (typeof val) {
case "string":
return make_node(AST_String, orig, {
@@ -400,9 +414,9 @@ merge(Compressor.prototype, {
return make_node(AST_Number, orig, { value: val });
case "boolean":
return make_node(val ? AST_True : AST_False, orig).transform(compressor);
return make_node(val ? AST_True : AST_False, orig);
case "undefined":
return make_node(AST_Undefined, orig).transform(compressor);
return make_node(AST_Undefined, orig);
default:
if (val === null) {
return make_node(AST_Null, orig, { value: null });
@@ -507,6 +521,7 @@ merge(Compressor.prototype, {
var self = compressor.self();
var var_defs_removed = false;
var toplevel = compressor.option("toplevel");
for (var stat_index = statements.length; --stat_index >= 0;) {
var stat = statements[stat_index];
if (stat instanceof AST_Definitions) continue;
@@ -544,7 +559,8 @@ merge(Compressor.prototype, {
// Only interested in cases with just one reference to the variable.
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;
continue;
}
@@ -910,7 +926,7 @@ merge(Compressor.prototype, {
if (stat instanceof AST_LoopControl) {
var lct = compressor.loopcontrol_target(stat.label);
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)) {
if (stat.label) {
@@ -1194,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 (Array.isArray(value)) return make_node(AST_Array, orig, {
elements: value.map(function(value) {
return to_node(compressor, value, orig);
return to_node(value, orig);
})
});
if (value && typeof value == "object") {
@@ -1206,14 +1222,14 @@ merge(Compressor.prototype, {
for (var key in value) {
props.push(make_node(AST_ObjectKeyVal, orig, {
key: key,
value: to_node(compressor, value[key], orig)
value: to_node(value[key], orig)
}));
}
return make_node(AST_Object, orig, {
properties: props
});
}
return make_node_from_constant(compressor, value, orig);
return make_node_from_constant(value, orig);
}
def(AST_Node, noop);
def(AST_Dot, function(compressor, suffix){
@@ -1224,7 +1240,7 @@ merge(Compressor.prototype, {
var name;
var defines = compressor.option("global_defs");
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);
node.walk(new TreeWalker(function(node) {
if (node instanceof AST_SymbolRef) {
@@ -1239,45 +1255,40 @@ merge(Compressor.prototype, {
node.DEFMETHOD("_find_defs", func);
});
function best_of(ast1, ast2) {
function best_of_expression(ast1, ast2) {
return ast1.print_to_string().length >
ast2.print_to_string().length
? ast2 : ast1;
}
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
}), make_node(AST_SimpleStatement, ast2, {
body: ast2
})).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
(function (def){
// The evaluate method returns an array with one or two
// elements. If the node has been successfully reduced to a
// constant, then the second element tells us the value;
// otherwise the second element is missing. The first element
// of the array is always an AST_Node descendant; if
// evaluation was successful it's a node that represents the
// constant; otherwise it's the original or a replacement node.
// If the node has been successfully reduced to a constant,
// then its value is returned; otherwise the element itself
// is returned.
// They can be distinguished as constant value is never a
// descendant of AST_Node.
AST_Node.DEFMETHOD("evaluate", function(compressor){
if (!compressor.option("evaluate")) return [ this ];
var val;
if (!compressor.option("evaluate")) return this;
try {
val = this._eval(compressor);
var val = this._eval(compressor);
return !val || val instanceof RegExp || typeof val != "object" ? val : this;
} catch(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("! ~ - +");
AST_Node.DEFMETHOD("is_constant", function(){
@@ -1315,8 +1326,8 @@ merge(Compressor.prototype, {
}));
}
var result = this.evaluate(compressor);
if (result.length > 1) {
return result[1];
if (result !== this) {
return result;
}
throw new Error(string_template("Cannot evaluate constant [{file}:{line},{col}]", this.start));
});
@@ -1476,9 +1487,9 @@ merge(Compressor.prototype, {
var stat = make_node(AST_SimpleStatement, 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(){
return basic_negation(this);
@@ -1642,8 +1653,8 @@ merge(Compressor.prototype, {
return thing && thing.aborts();
};
(function(def){
def(AST_Statement, function(){ return null });
def(AST_Jump, function(){ return this });
def(AST_Statement, return_null);
def(AST_Jump, return_this);
function block_aborts(){
var n = this.body.length;
return n > 0 && aborts(this.body[n - 1]);
@@ -1660,7 +1671,7 @@ merge(Compressor.prototype, {
/* -----[ optimizers ]----- */
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 self;
@@ -1810,10 +1821,12 @@ merge(Compressor.prototype, {
node.name = null;
}
if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) {
if (!compressor.option("keep_fargs")) {
var trim = !compressor.option("keep_fargs");
for (var a = node.argnames, i = a.length; --i >= 0;) {
var sym = a[i];
if (!(sym.definition().id in in_use_ids)) {
sym.__unused = true;
if (trim) {
a.pop();
compressor.warn("Dropping unused function argument {name} [{file}:{line},{col}]", {
name : sym.name,
@@ -1822,7 +1835,9 @@ merge(Compressor.prototype, {
col : sym.start.col
});
}
else break;
}
else {
trim = false;
}
}
}
@@ -1973,7 +1988,7 @@ merge(Compressor.prototype, {
vars.set(def.name.name, def);
++vars_found;
});
var seq = node.to_assignments();
var seq = node.to_assignments(compressor);
var p = tt.parent();
if (p instanceof AST_ForIn && p.init === node) {
if (seq == null) {
@@ -2069,14 +2084,6 @@ merge(Compressor.prototype, {
// drop_side_effect_free()
// remove side-effect-free parts which only affects return value
(function(def){
function return_this() {
return this;
}
function return_null() {
return null;
}
// Drop side-effect-free elements from an array of expressions.
// Returns an array of expressions with side-effects or null
// if all elements were dropped. Note: original array may be
@@ -2233,27 +2240,24 @@ merge(Compressor.prototype, {
});
OPT(AST_DWLoop, function(self, compressor){
var cond = self.condition.evaluate(compressor);
self.condition = cond[0];
if (!compressor.option("loops")) return self;
if (cond.length > 1) {
if (cond[1]) {
var cond = self.condition.evaluate(compressor);
if (cond !== self.condition) {
if (cond) {
return make_node(AST_For, self, {
body: self.body
});
} else if (self instanceof AST_While) {
if (compressor.option("dead_code")) {
} else if (compressor.option("dead_code") && self instanceof AST_While) {
var a = [];
extract_declarations_from_unreachable_code(compressor, self.body, a);
return make_node(AST_BlockStatement, self, { body: a });
}
} else {
// self instanceof AST_Do
return self;
cond = make_node_from_constant(cond, self.condition).transform(compressor);
self.condition = best_of_expression(cond, self.condition);
}
}
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;
});
@@ -2275,7 +2279,7 @@ merge(Compressor.prototype, {
var first = self.body instanceof AST_BlockStatement ? self.body.body[0] : self.body;
if (first instanceof AST_If) {
if (first.body instanceof AST_Break
&& compressor.loopcontrol_target(first.body.label) === self) {
&& compressor.loopcontrol_target(first.body.label) === compressor.self()) {
if (self.condition) {
self.condition = make_node(AST_Binary, self.condition, {
left: self.condition,
@@ -2288,7 +2292,7 @@ merge(Compressor.prototype, {
drop_it(first.alternative);
}
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) {
self.condition = make_node(AST_Binary, self.condition, {
left: self.condition,
@@ -2304,15 +2308,10 @@ merge(Compressor.prototype, {
};
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 (cond) {
if (cond.length > 1 && !cond[1]) {
if (compressor.option("dead_code")) {
if (self.condition) {
var cond = self.condition.evaluate(compressor);
if (compressor.option("dead_code") && !cond) {
var a = [];
if (self.init instanceof AST_Statement) {
a.push(self.init);
@@ -2325,6 +2324,9 @@ merge(Compressor.prototype, {
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);
@@ -2340,9 +2342,8 @@ merge(Compressor.prototype, {
// “has no side effects”; also it doesn't work for cases like
// `x && true`, though it probably should.
var cond = self.condition.evaluate(compressor);
self.condition = cond[0];
if (cond.length > 1) {
if (cond[1]) {
if (cond !== self.condition) {
if (cond) {
compressor.warn("Condition always true [{file}:{line},{col}]", self.condition.start);
if (compressor.option("dead_code")) {
var a = [];
@@ -2350,7 +2351,7 @@ merge(Compressor.prototype, {
extract_declarations_from_unreachable_code(compressor, self.alternative, a);
}
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 {
compressor.warn("Condition always false [{file}:{line},{col}]", self.condition.start);
@@ -2358,9 +2359,11 @@ merge(Compressor.prototype, {
var a = [];
extract_declarations_from_unreachable_code(compressor, self.body, a);
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 self_condition_length = self.condition.print_to_string().length;
@@ -2377,8 +2380,8 @@ merge(Compressor.prototype, {
}
if (is_empty(self.body) && is_empty(self.alternative)) {
return make_node(AST_SimpleStatement, self.condition, {
body: self.condition
}).transform(compressor);
body: self.condition.clone()
}).optimize(compressor);
}
if (self.body instanceof AST_SimpleStatement
&& self.alternative instanceof AST_SimpleStatement) {
@@ -2388,7 +2391,7 @@ merge(Compressor.prototype, {
consequent : statement_to_expression(self.body),
alternative : statement_to_expression(self.alternative)
})
}).transform(compressor);
}).optimize(compressor);
}
if (is_empty(self.alternative) && self.body instanceof AST_SimpleStatement) {
if (self_condition_length === negated_length && !negated_is_best
@@ -2404,14 +2407,14 @@ merge(Compressor.prototype, {
left : negated,
right : statement_to_expression(self.body)
})
}).transform(compressor);
}).optimize(compressor);
return make_node(AST_SimpleStatement, self, {
body: make_node(AST_Binary, self, {
operator : "&&",
left : self.condition,
right : statement_to_expression(self.body)
})
}).transform(compressor);
}).optimize(compressor);
}
if (self.body instanceof AST_EmptyStatement
&& self.alternative
@@ -2422,7 +2425,7 @@ merge(Compressor.prototype, {
left : self.condition,
right : statement_to_expression(self.alternative)
})
}).transform(compressor);
}).optimize(compressor);
}
if (self.body instanceof AST_Exit
&& self.alternative instanceof AST_Exit
@@ -2432,18 +2435,21 @@ merge(Compressor.prototype, {
condition : self.condition,
consequent : self.body.value || make_node(AST_Undefined, self.body),
alternative : self.alternative.value || make_node(AST_Undefined, self.alternative)
})
}).transform(compressor);
}).transform(compressor)
}).optimize(compressor);
}
if (self.body instanceof AST_If
&& !self.body.alternative
&& !self.alternative) {
self.condition = make_node(AST_Binary, self.condition, {
self = make_node(AST_If, self, {
condition: make_node(AST_Binary, self.condition, {
operator: "&&",
left: self.condition,
right: self.body.condition
}).transform(compressor);
self.body = self.body.body;
}),
body: self.body.body,
alternative: null
});
}
if (aborts(self.body)) {
if (self.alternative) {
@@ -2451,7 +2457,7 @@ merge(Compressor.prototype, {
self.alternative = null;
return make_node(AST_BlockStatement, self, {
body: [ self, alt ]
}).transform(compressor);
}).optimize(compressor);
}
}
if (aborts(self.alternative)) {
@@ -2461,7 +2467,7 @@ merge(Compressor.prototype, {
self.alternative = null;
return make_node(AST_BlockStatement, self, {
body: [ self, body ]
}).transform(compressor);
}).optimize(compressor);
}
return self;
});
@@ -2485,12 +2491,12 @@ merge(Compressor.prototype, {
}
break;
}
var exp = self.expression.evaluate(compressor);
out: if (exp.length == 2) try {
var value = self.expression.evaluate(compressor);
out: if (value !== self.expression) try {
// 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;
var value = exp[1];
var in_if = false;
var in_block = false;
var started = false;
@@ -2537,11 +2543,11 @@ merge(Compressor.prototype, {
if (stopped) return MAP.skip;
if (node instanceof AST_Case) {
var exp = node.expression.evaluate(compressor);
if (exp.length < 2) {
if (exp === node.expression) {
// got a case with non-constant expression, baling out
throw self;
}
if (exp[1] === value || started) {
if (exp === value || started) {
started = true;
if (aborts(node)) stopped = true;
descend(node, this);
@@ -2575,7 +2581,8 @@ merge(Compressor.prototype, {
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){
if (def.value) {
var name = make_node(AST_SymbolRef, def.name, def.name);
@@ -2584,6 +2591,7 @@ merge(Compressor.prototype, {
left : name,
right : def.value
}));
if (reduce_vars) name.definition().fixed = false;
}
return a;
}, []);
@@ -2609,6 +2617,9 @@ merge(Compressor.prototype, {
exp = def.fixed;
if (compressor.option("unused")
&& def.references.length == 1
&& !(def.scope.uses_arguments
&& def.orig[0] instanceof AST_SymbolFunarg)
&& !def.scope.uses_eval
&& compressor.find_parent(AST_Scope) === def.scope) {
self.expression = exp;
}
@@ -2617,16 +2628,26 @@ merge(Compressor.prototype, {
if (compressor.option("unused")
&& exp instanceof AST_Function
&& !exp.uses_arguments
&& !exp.uses_eval
&& self.args.length > exp.argnames.length) {
var end = exp.argnames.length;
for (var i = end, len = self.args.length; i < len; i++) {
&& !exp.uses_eval) {
var pos = 0, last = 0;
for (var i = 0, len = self.args.length; i < len; i++) {
var trim = i >= exp.argnames.length;
if (trim || exp.argnames[i].__unused) {
var node = self.args[i].drop_side_effect_free(compressor);
if (node) {
self.args[end++] = node;
self.args[pos++] = node;
} else if (!trim) {
self.args[pos++] = make_node(AST_Number, self.args[i], {
value: 0
});
continue;
}
} else {
self.args[pos++] = self.args[i];
}
self.args.length = end;
last = pos;
}
self.args.length = last;
}
if (compressor.option("unsafe")) {
if (exp instanceof AST_SymbolRef && exp.undeclared()) {
@@ -2742,15 +2763,14 @@ merge(Compressor.prototype, {
var separator;
if (self.args.length > 0) {
separator = self.args[0].evaluate(compressor);
if (separator.length < 2) break EXIT; // not a constant
separator = separator[1];
if (separator === self.args[0]) break EXIT; // not a constant
}
var elements = [];
var consts = [];
exp.expression.elements.forEach(function(el) {
el = el.evaluate(compressor);
if (el.length > 1) {
consts.push(el[1]);
var value = el.evaluate(compressor);
if (value !== el) {
consts.push(value);
} else {
if (consts.length > 0) {
elements.push(make_node(AST_String, self, {
@@ -2758,7 +2778,7 @@ merge(Compressor.prototype, {
}));
consts.length = 0;
}
elements.push(el[0]);
elements.push(el);
}
});
if (consts.length > 0) {
@@ -2799,7 +2819,7 @@ merge(Compressor.prototype, {
node.expression = node.expression.clone();
node.expression.expression = node.expression.expression.clone();
node.expression.expression.elements = elements;
return best_of(self, node);
return best_of(compressor, self, node);
}
}
if (exp instanceof AST_Function) {
@@ -2945,8 +2965,7 @@ merge(Compressor.prototype, {
return e.expression;
}
if (e instanceof AST_Binary) {
var statement = first_in_statement(compressor);
self = (statement ? best_of_statement : best_of)(self, e.negate(compressor, statement));
self = best_of(compressor, self, e.negate(compressor, first_in_statement(compressor)));
}
break;
case "typeof":
@@ -2959,7 +2978,15 @@ merge(Compressor.prototype, {
}).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) {
@@ -2997,16 +3024,6 @@ merge(Compressor.prototype, {
var commutativeOperators = makePredicate("== === != !== * & | ^");
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() {
return self.left instanceof AST_Constant
|| self.right instanceof AST_Constant
@@ -3091,48 +3108,48 @@ merge(Compressor.prototype, {
case "&&":
var ll = self.left.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);
return make_node(AST_Seq, self, {
car: self.left,
cdr: make_node(AST_False, self)
}).optimize(compressor);
}
if (ll.length > 1 && ll[1]) {
return rr[0];
if (ll !== self.left && ll) {
return self.right.optimize(compressor);
}
if (rr.length > 1 && rr[1]) {
return ll[0];
if (rr !== self.right && rr) {
return self.left.optimize(compressor);
}
break;
case "||":
var ll = self.left.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);
return make_node(AST_Seq, self, {
car: self.left,
cdr: make_node(AST_True, self)
}).optimize(compressor);
}
if (ll.length > 1 && !ll[1]) {
return rr[0];
if (!ll) {
return self.right.optimize(compressor);
}
if (rr.length > 1 && !rr[1]) {
return ll[0];
if (!rr) {
return self.left.optimize(compressor);
}
break;
case "+":
var ll = self.left.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);
return make_node(AST_Seq, self, {
car: self.right,
cdr: make_node(AST_True, self)
}).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);
return make_node(AST_Seq, self, {
car: self.left,
@@ -3144,12 +3161,11 @@ merge(Compressor.prototype, {
if (compressor.option("comparisons") && self.is_boolean()) {
if (!(compressor.parent() instanceof AST_Binary)
|| compressor.parent() instanceof AST_Assign) {
var statement = first_in_statement(compressor);
var negated = make_node(AST_UnaryPrefix, self, {
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")) {
switch (self.operator) {
@@ -3301,9 +3317,9 @@ merge(Compressor.prototype, {
});
if (self.right instanceof AST_Constant
&& !(self.left instanceof AST_Constant)) {
self = best_of(reversed, self);
self = best_of(compressor, reversed, self);
} else {
self = best_of(self, reversed);
self = best_of(compressor, self, reversed);
}
}
if (associative && self.is_number(compressor)) {
@@ -3400,7 +3416,12 @@ merge(Compressor.prototype, {
self.right = self.right.right;
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){
@@ -3415,11 +3436,11 @@ merge(Compressor.prototype, {
&& (!self.scope.uses_with || !compressor.find_parent(AST_With))) {
switch (self.name) {
case "undefined":
return make_node(AST_Undefined, self).transform(compressor);
return make_node(AST_Undefined, self).optimize(compressor);
case "NaN":
return make_node(AST_NaN, self).transform(compressor);
return make_node(AST_NaN, self).optimize(compressor);
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")) {
@@ -3427,18 +3448,20 @@ merge(Compressor.prototype, {
if (d.fixed) {
if (d.should_replace === undefined) {
var init = d.fixed.evaluate(compressor);
if (init.length > 1) {
var value = init[0].print_to_string().length;
if (init !== d.fixed) {
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 freq = d.references.length;
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 {
d.should_replace = false;
}
}
if (d.should_replace) {
return d.should_replace;
return d.should_replace.clone(true);
}
}
}
@@ -3503,8 +3526,8 @@ merge(Compressor.prototype, {
return AST_Seq.cons(car, self);
}
var cond = self.condition.evaluate(compressor);
if (cond.length > 1) {
if (cond[1]) {
if (cond !== self.condition) {
if (cond) {
compressor.warn("Condition always true [{file}:{line},{col}]", self.start);
return maintain_this_binding(compressor.parent(), self, self.consequent);
} else {
@@ -3512,9 +3535,8 @@ merge(Compressor.prototype, {
return maintain_this_binding(compressor.parent(), self, self.alternative);
}
}
var statement = first_in_statement(compressor);
var negated = cond[0].negate(compressor, statement);
if ((statement ? best_of_statement : best_of)(cond[0], negated) === negated) {
var negated = cond.negate(compressor, first_in_statement(compressor));
if (best_of(compressor, cond, negated) === negated) {
self = make_node(AST_Conditional, self, {
condition: negated,
consequent: self.alternative,
@@ -3694,7 +3716,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){
@@ -3733,13 +3760,17 @@ merge(Compressor.prototype, {
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) {
if (compressor.option("booleans") && compressor.in_boolean_context()) {
var best = first_in_statement(compressor) ? best_of_statement : best_of;
return best(self, make_node(AST_Seq, self, {
return best_of(compressor, self, make_node(AST_Seq, self, {
car: self,
cdr: make_node(AST_True, self)
}).optimize(compressor));

View File

@@ -928,11 +928,9 @@ function parse($TEXT, options) {
expression : parenthesised(),
body : statement()
});
default:
}
}
unexpected();
}
}
});
function labeled_statement() {

View File

@@ -212,9 +212,10 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
self.walk(new TreeWalker(function(node, descend) {
if (node instanceof AST_SymbolCatch) {
var name = node.name;
var refs = node.thedef.references;
var scope = node.thedef.scope.parent_scope;
var def = scope.find_variable(name) || self.globals.get(name) || scope.def_variable(node);
node.thedef.references.forEach(function(ref) {
refs.forEach(function(ref) {
ref.thedef = def;
ref.reference(options);
});

View File

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

View File

@@ -4,7 +4,7 @@
"homepage": "http://lisperator.net/uglifyjs",
"author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
"license": "BSD-2-Clause",
"version": "2.8.10",
"version": "2.8.13",
"engines": {
"node": ">=0.8.0"
},

View File

@@ -1152,7 +1152,8 @@ collapse_vars_arguments: {
options = {
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,
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: {
var outer = function() {
@@ -1335,6 +1336,7 @@ issue_1537: {
issue_1562: {
options = {
collapse_vars: true,
toplevel: true,
}
input: {
var v = 1, B = 2;
@@ -1363,3 +1365,46 @@ issue_1562: {
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

@@ -761,3 +761,33 @@ assign_chain: {
}
}
}
issue_1583: {
options = {
keep_fargs: true,
reduce_vars: true,
unused: true,
}
input: {
function m(t) {
(function(e) {
t = e();
})(function() {
return (function(a) {
return a;
})(function(a) {});
});
}
}
expect: {
function m(t) {
(function(e) {
t = (function() {
return (function(a) {
return a;
})(function(a) {});
})();
})();
}
}
}

View File

@@ -0,0 +1,87 @@
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
}

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

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

View File

@@ -295,7 +295,15 @@ issue_186_beautify: {
else
bar();
}
expect_exact: 'var x = 3;\n\nif (foo()) do {\n do {\n alert(x);\n } while (--x);\n} while (x); else bar();'
expect_exact: [
'var x = 3;',
'',
'if (foo()) do {',
' do {',
' alert(x);',
' } while (--x);',
'} while (x); else bar();',
]
}
issue_186_beautify_ie8: {
@@ -314,7 +322,17 @@ issue_186_beautify_ie8: {
else
bar();
}
expect_exact: 'var x = 3;\n\nif (foo()) {\n do {\n do {\n alert(x);\n } while (--x);\n } while (x);\n} else bar();'
expect_exact: [
'var x = 3;',
'',
'if (foo()) {',
' do {',
' do {',
' alert(x);',
' } while (--x);',
' } while (x);',
'} else bar();',
]
}
issue_186_bracketize: {
@@ -374,7 +392,19 @@ issue_186_beautify_bracketize: {
else
bar();
}
expect_exact: 'var x = 3;\n\nif (foo()) {\n do {\n do {\n alert(x);\n } while (--x);\n } while (x);\n} else {\n bar();\n}'
expect_exact: [
'var x = 3;',
'',
'if (foo()) {',
' do {',
' do {',
' alert(x);',
' } while (--x);',
' } while (x);',
'} else {',
' bar();',
'}',
]
}
issue_186_beautify_bracketize_ie8: {
@@ -394,5 +424,17 @@ issue_186_beautify_bracketize_ie8: {
else
bar();
}
expect_exact: 'var x = 3;\n\nif (foo()) {\n do {\n do {\n alert(x);\n } while (--x);\n } while (x);\n} else {\n bar();\n}'
expect_exact: [
'var x = 3;',
'',
'if (foo()) {',
' do {',
' do {',
' alert(x);',
' } while (--x);',
' } while (x);',
'} else {',
' bar();',
'}',
]
}

View File

@@ -7,7 +7,13 @@ too_short: {
return { c: 42, d: a(), e: "foo"};
}
}
expect_exact: 'function f(a){\nreturn{\nc:42,\nd:a(),\ne:"foo"}}'
expect_exact: [
'function f(a){',
'return{',
'c:42,',
'd:a(),',
'e:"foo"}}',
]
expect_warnings: [
"WARN: Output exceeds 10 characters"
]
@@ -22,7 +28,12 @@ just_enough: {
return { c: 42, d: a(), e: "foo"};
}
}
expect_exact: 'function f(a){\nreturn{c:42,\nd:a(),e:"foo"}\n}'
expect_exact: [
'function f(a){',
'return{c:42,',
'd:a(),e:"foo"}',
'}',
]
expect_warnings: [
]
}

View File

@@ -1144,3 +1144,210 @@ double_reference: {
}
}
}
iife_arguments_1: {
options = {
reduce_vars: true,
unused: true,
}
input: {
(function(x) {
console.log(x() === arguments[0]);
})(function f() {
return f;
});
}
expect: {
(function(x) {
console.log(x() === arguments[0]);
})(function f() {
return f;
});
}
}
iife_arguments_2: {
options = {
reduce_vars: true,
unused: true,
}
input: {
(function() {
var x = function f() {
return f;
};
console.log(x() === arguments[0]);
})();
}
expect: {
(function() {
console.log(function f() {
return f;
}() === arguments[0]);
})();
}
}
iife_eval_1: {
options = {
reduce_vars: true,
unused: true,
}
input: {
(function(x) {
console.log(x() === eval("x"));
})(function f() {
return f;
});
}
expect: {
(function(x) {
console.log(x() === eval("x"));
})(function f() {
return f;
});
}
}
iife_eval_2: {
options = {
reduce_vars: true,
unused: true,
}
input: {
(function() {
var x = function f() {
return f;
};
console.log(x() === eval("x"));
})();
}
expect: {
(function() {
var x = function f() {
return f;
};
console.log(x() === eval("x"));
})();
}
}
iife_func_side_effects: {
options = {
reduce_vars: true,
unused: true,
}
input: {
(function(a, b, c) {
return b();
})(x(), function() {
return y();
}, z());
}
expect: {
(function(a, b, c) {
return function() {
return y();
}();
})(x(), 0, z());
}
}
issue_1595_1: {
options = {
evaluate: true,
reduce_vars: true,
unused: true,
}
input: {
(function f(a) {
return f(a + 1);
})(2);
}
expect: {
(function f(a) {
return f(a + 1);
})(2);
}
}
issue_1595_2: {
options = {
evaluate: true,
reduce_vars: true,
unused: true,
}
input: {
(function f(a) {
return g(a + 1);
})(2);
}
expect: {
(function(a) {
return g(a + 1);
})(2);
}
}
issue_1595_3: {
options = {
evaluate: true,
passes: 2,
reduce_vars: true,
unused: true,
}
input: {
(function f(a) {
return g(a + 1);
})(2);
}
expect: {
(function(a) {
return g(3);
})();
}
}
issue_1595_4: {
options = {
evaluate: true,
reduce_vars: true,
unused: true,
}
input: {
(function iife(a, b, c) {
console.log(a, b, c);
if (a) iife(a - 1, b, c);
})(3, 4, 5);
}
expect: {
(function iife(a, b, c) {
console.log(a, b, c);
if (a) iife(a - 1, b, c);
})(3, 4, 5);
}
}
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

@@ -182,3 +182,39 @@ reduce_vars: {
}
}
}
issue_1586_1: {
options = {
screw_ie8: false,
}
mangle = {
screw_ie8: false,
}
input: {
function f() {
try {
} catch (err) {
console.log(err.message);
}
}
}
expect_exact: "function f(){try{}catch(c){console.log(c.message)}}"
}
issue_1586_2: {
options = {
screw_ie8: true,
}
mangle = {
screw_ie8: true,
}
input: {
function f() {
try {
} catch (err) {
console.log(err.message);
}
}
}
expect_exact: "function f(){try{}catch(c){console.log(c.message)}}"
}

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

@@ -0,0 +1,129 @@
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);
}
}
booleans_global_defs: {
options = {
booleans: true,
evaluate: true,
global_defs: {
A: true,
},
}
input: {
console.log(A == 1);
}
expect: {
console.log(!0);
}
}
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

@@ -0,0 +1 @@
for (var i = 0; i < 1; i++)

View File

@@ -152,7 +152,7 @@ describe("bin/uglifyjs", function () {
});
});
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) {
if (err) throw err;
@@ -238,4 +238,17 @@ describe("bin/uglifyjs", function () {
done();
});
});
it("Should fail with a missing loop body", function(done) {
var command = uglifyjscmd + ' test/input/invalid/loop-no-body.js';
exec(command, function (err, stdout, stderr) {
assert.ok(err);
var lines = stderr.split(/\n/);
assert.strictEqual(lines[0], "Parse error at test/input/invalid/loop-no-body.js:2,0");
assert.strictEqual(lines[1], "for (var i = 0; i < 1; i++) ");
assert.strictEqual(lines[2], " ^");
assert.strictEqual(lines[3], "SyntaxError: Unexpected token: eof (undefined)");
done();
});
});
});

View File

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

View File

@@ -6,6 +6,7 @@ var U = require("../tools/node");
var path = require("path");
var fs = require("fs");
var assert = require("assert");
var vm = require("vm");
var tests_dir = path.dirname(module.filename);
var failures = 0;
@@ -165,6 +166,51 @@ function run_compress_tests() {
failed_files[file] = 1;
}
}
if (test.expect_stdout) {
try {
var stdout = run_code(input_code);
if (test.expect_stdout === true) {
test.expect_stdout = stdout;
}
if (test.expect_stdout != stdout) {
log("!!! Invalid input or expected stdout\n---INPUT---\n{input}\n---EXPECTED STDOUT---\n{expected}\n---ACTUAL STDOUT---\n{actual}\n\n", {
input: input_code,
expected: test.expect_stdout,
actual: stdout,
});
failures++;
failed_files[file] = 1;
} else {
try {
stdout = run_code(output);
if (test.expect_stdout != stdout) {
log("!!! failed\n---INPUT---\n{input}\n---EXPECTED STDOUT---\n{expected}\n---ACTUAL STDOUT---\n{actual}\n\n", {
input: input_code,
expected: test.expect_stdout,
actual: stdout,
});
failures++;
failed_files[file] = 1;
}
} catch (ex) {
log("!!! Execution of output failed\n---INPUT---\n{input}\n---OUTPUT---\n{output}\n--ERROR--\n{error}\n\n", {
input: input_code,
output: output,
error: ex.toString(),
});
failures++;
failed_files[file] = 1;
}
}
} catch (ex) {
log("!!! Execution of input failed\n---INPUT---\n{input}\n--ERROR--\n{error}\n\n", {
input: input_code,
error: ex.toString(),
});
failures++;
failed_files[file] = 1;
}
}
}
}
var tests = parse_test(path.resolve(dir, file));
@@ -214,6 +260,23 @@ function parse_test(file) {
}));
}
function read_string(stat) {
if (stat.TYPE == "SimpleStatement") {
var body = stat.body;
switch(body.TYPE) {
case "String":
return body.value;
case "Array":
return body.elements.map(function(element) {
if (element.TYPE !== "String")
throw new Error("Should be array of strings");
return element.value;
}).join("\n");
}
}
throw new Error("Should be string or array of strings");
}
function get_one_test(name, block) {
var test = { name: name, options: {} };
var tw = new U.TreeWalker(function(node, descend){
@@ -226,12 +289,13 @@ function parse_test(file) {
return true;
}
if (node instanceof U.AST_LabeledStatement) {
var label = node.label;
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}]", {
name: node.label.name,
line: node.label.start.line,
col: node.label.start.col
name: label.name,
line: label.start.line,
col: label.start.col
})
);
var stat = node.body;
@@ -239,15 +303,16 @@ function parse_test(file) {
if (stat.body.length == 1) stat = stat.body[0];
else if (stat.body.length == 0) stat = new U.AST_EmptyStatement();
}
if (node.label.name === "expect_exact") {
if (!(stat.TYPE === "SimpleStatement" && stat.body.TYPE === "String")) {
throw new Error(
"The value of the expect_exact clause should be a string, " +
"like `expect_exact: \"some.exact.javascript;\"`");
}
test[node.label.name] = stat.body.start.value
if (label.name == "expect_exact") {
test[label.name] = read_string(stat);
} else if (label.name == "expect_stdout") {
if (stat.TYPE == "SimpleStatement" && stat.body instanceof U.AST_Boolean) {
test[label.name] = stat.body.value;
} else {
test[node.label.name] = stat;
test[label.name] = read_string(stat) + "\n";
}
} else {
test[label.name] = stat;
}
return true;
}
@@ -269,3 +334,17 @@ function evaluate(code) {
code = make_code(code, { beautify: true });
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;
} finally {
process.stdout.write = original_write;
}
}