Merge branch 'master' into harmony-v2.8.17

This commit is contained in:
alexlamsl
2017-03-28 22:03:46 +08:00
30 changed files with 2842 additions and 232 deletions

View File

@@ -1,7 +1,9 @@
- Bug report or feature request?
- Bug report or feature request? <!-- Note: sub-optimal but correct code is not a bug -->
- `uglify-js` version (`uglifyjs -V`)
- JavaScript input - ideally as small as possible.
- The `uglifyjs` CLI command executed or `minify()` options used.
- An example of JavaScript output produced and/or the error or warning.
Note: the release version of `uglify-js` only supports ES5. Those wishing to minify ES6 should use the experimental [`harmony`](https://github.com/mishoo/UglifyJS2#harmony) branch.
<!--
Note: the release version of uglify-js only supports ES5. Those wishing
to minify ES6 should use the experimental harmony branch.
-->

View File

@@ -561,7 +561,7 @@ function getOptions(flag, constants) {
var ast;
try {
ast = UglifyJS.parse(x, { expression: true });
ast = UglifyJS.parse(x, { cli: true, expression: true });
} catch(ex) {
if (ex instanceof UglifyJS.JS_Parse_Error) {
print_error("Error parsing arguments for flag `" + flag + "': " + x);

View File

@@ -261,7 +261,7 @@ merge(Compressor.prototype, {
if (node instanceof AST_SymbolRef) {
var d = node.definition();
d.references.push(node);
if (!d.fixed || !is_safe(d)
if (d.fixed === undefined || !is_safe(d)
|| is_modified(node, 0, d.fixed instanceof AST_Lambda)) {
d.fixed = false;
}
@@ -274,10 +274,10 @@ merge(Compressor.prototype, {
node.name.walk(suppressor);
} else {
var d = node.name.definition();
if (d.fixed === undefined) {
d.fixed = node.value || make_node(AST_Undefined, node);
if (d.fixed == null) {
d.fixed = node.value;
mark_as_safe(d);
} else {
} else if (node.value) {
d.fixed = false;
}
}
@@ -346,7 +346,7 @@ merge(Compressor.prototype, {
pop();
return true;
}
if (node instanceof AST_Catch) {
if (node instanceof AST_Catch || node instanceof AST_SwitchBranch) {
push();
descend();
pop();
@@ -362,7 +362,14 @@ merge(Compressor.prototype, {
function is_safe(def) {
for (var i = safe_ids.length, id = def.id; --i >= 0;) {
if (safe_ids[i][id]) return true;
if (safe_ids[i][id]) {
if (def.fixed == null) {
var orig = def.orig[0];
if (orig instanceof AST_SymbolFunarg || orig.name == "arguments") return false;
def.fixed = make_node(AST_Undefined, orig);
}
return true;
}
}
}
@@ -488,15 +495,6 @@ merge(Compressor.prototype, {
return x;
};
var readOnlyPrefix = makePredicate("! ~ + - void typeof");
function statement_to_expression(stat) {
if (stat.body instanceof AST_UnaryPrefix && readOnlyPrefix(stat.body.operator)) {
return stat.body.expression;
} else {
return stat.body;
}
}
function is_iife_call(node) {
if (node instanceof AST_Call && !(node instanceof AST_New)) {
return node.expression instanceof AST_Function || is_iife_call(node.expression);
@@ -606,9 +604,10 @@ merge(Compressor.prototype, {
// Restrict var replacement to constants if side effects encountered.
if (side_effects_encountered |= lvalues_encountered) continue;
var value_has_side_effects = var_decl.value.has_side_effects(compressor);
// Non-constant single use vars can only be replaced in same scope.
if (ref.scope.get_defun_scope() !== self) {
side_effects_encountered |= var_decl.value.has_side_effects(compressor);
side_effects_encountered |= value_has_side_effects;
continue;
}
@@ -635,6 +634,7 @@ merge(Compressor.prototype, {
|| (parent instanceof AST_If && node !== parent.condition)
|| (parent instanceof AST_Conditional && node !== parent.condition)
|| (node instanceof AST_SymbolRef
&& value_has_side_effects
&& !are_references_in_scope(node.definition(), self))
|| (parent instanceof AST_Binary
&& (parent.operator == "&&" || parent.operator == "||")
@@ -998,10 +998,10 @@ merge(Compressor.prototype, {
};
statements.forEach(function(stat){
if (stat instanceof AST_SimpleStatement) {
if (seqLength(seq) >= compressor.sequences_limit) {
push_seq();
}
seq.push(seq.length > 0 ? statement_to_expression(stat) : stat.body);
if (seqLength(seq) >= compressor.sequences_limit) push_seq();
var body = stat.body;
if (seq.length > 0) body = body.drop_side_effect_free(compressor);
if (body) seq.push(body);
} else {
push_seq();
ret.push(stat);
@@ -1050,7 +1050,7 @@ merge(Compressor.prototype, {
stat.init = cons_seq(stat.init);
}
else if (!stat.init) {
stat.init = statement_to_expression(prev);
stat.init = prev.body.drop_side_effect_free(compressor);
ret.pop();
}
} catch(ex) {
@@ -1190,9 +1190,9 @@ merge(Compressor.prototype, {
&& this.left.is_number(compressor)
&& this.right.is_number(compressor);
});
var assign = makePredicate("-= *= /= %= &= |= ^= <<= >>= >>>=");
def(AST_Assign, function(compressor){
return assign(this.operator) || this.right.is_number(compressor);
return binary(this.operator.slice(0, -1))
|| this.operator == "=" && this.right.is_number(compressor);
});
def(AST_Seq, function(compressor){
return this.cdr.is_number(compressor);
@@ -1231,8 +1231,10 @@ merge(Compressor.prototype, {
node.DEFMETHOD("is_string", func);
});
var unary_side_effects = makePredicate("delete ++ --");
function isLHS(node, parent) {
return parent instanceof AST_Unary && (parent.operator == "++" || parent.operator == "--")
return parent instanceof AST_Unary && unary_side_effects(parent.operator)
|| parent instanceof AST_Assign && parent.left === node;
}
@@ -1636,14 +1638,37 @@ merge(Compressor.prototype, {
return false;
});
def(AST_Block, function(compressor){
for (var i = this.body.length; --i >= 0;) {
if (this.body[i].has_side_effects(compressor))
function any(list, compressor) {
for (var i = list.length; --i >= 0;)
if (list[i].has_side_effects(compressor))
return true;
}
return false;
});
}
def(AST_Block, function(compressor){
return any(this.body, compressor);
});
def(AST_Switch, function(compressor){
return this.expression.has_side_effects(compressor)
|| any(this.body, compressor);
});
def(AST_Case, function(compressor){
return this.expression.has_side_effects(compressor)
|| any(this.body, compressor);
});
def(AST_Try, function(compressor){
return any(this.body, compressor)
|| this.bcatch && this.bcatch.has_side_effects(compressor)
|| this.bfinally && this.bfinally.has_side_effects(compressor);
});
def(AST_If, function(compressor){
return this.condition.has_side_effects(compressor)
|| this.body && this.body.has_side_effects(compressor)
|| this.alternative && this.alternative.has_side_effects(compressor);
});
def(AST_LabeledStatement, function(compressor){
return this.body.has_side_effects(compressor);
});
def(AST_SimpleStatement, function(compressor){
return this.body.has_side_effects(compressor);
});
@@ -1662,17 +1687,14 @@ merge(Compressor.prototype, {
|| this.alternative.has_side_effects(compressor);
});
def(AST_Unary, function(compressor){
return member(this.operator, ["delete", "++", "--"])
return unary_side_effects(this.operator)
|| this.expression.has_side_effects(compressor);
});
def(AST_SymbolRef, function(compressor){
return this.global() && this.undeclared();
});
def(AST_Object, function(compressor){
for (var i = this.properties.length; --i >= 0;)
if (this.properties[i].has_side_effects(compressor))
return true;
return false;
return any(this.properties, compressor);
});
def(AST_ObjectProperty, function(compressor){
if (this.key instanceof AST_ObjectKeyVal &&
@@ -1681,10 +1703,7 @@ merge(Compressor.prototype, {
return this.value.has_side_effects(compressor);
});
def(AST_Array, function(compressor){
for (var i = this.elements.length; --i >= 0;)
if (this.elements[i].has_side_effects(compressor))
return true;
return false;
return any(this.elements, compressor);
});
def(AST_Dot, function(compressor){
if (!compressor.option("pure_getters")) return true;
@@ -1927,9 +1946,13 @@ merge(Compressor.prototype, {
function before(node, descend, in_list) {
if (node instanceof AST_Function
&& node.name
&& !compressor.option("keep_fnames")
&& !(node.name.definition().id in in_use_ids)) {
node.name = null;
&& !compressor.option("keep_fnames")) {
var def = node.name.definition();
// any declarations with same name will overshadow
// name of this anonymous function and can therefore
// never be used anywhere
if (!(def.id in in_use_ids) || def.orig.length > 1)
node.name = null;
}
if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) {
var trim = !compressor.option("keep_fargs");
@@ -1980,9 +2003,14 @@ merge(Compressor.prototype, {
var def = node.definitions.filter(function(def){
if (def.value) def.value = def.value.transform(tt);
if (def.name instanceof AST_Destructuring) return true;
if (def.name.definition().id in in_use_ids) return true;
if (!drop_vars && def.name.definition().global) return true;
var sym = def.name.definition();
if (sym.id in in_use_ids) return true;
if (!drop_vars && sym.global) return true;
if (sym.orig[0] instanceof AST_SymbolCatch
&& sym.scope.parent_scope.find_variable(def.name).orig[0] === def.name) {
def.value = def.value && def.value.drop_side_effect_free(compressor);
return true;
}
var w = {
name : def.name.name,
file : def.name.start.file,
@@ -2069,6 +2097,9 @@ merge(Compressor.prototype, {
return in_list ? MAP.splice(body) : make_node(AST_BlockStatement, node, {
body: body
});
} else if (is_empty(node.init)) {
node.init = null;
return node;
}
}
if (node instanceof AST_BlockStatement) {
@@ -2266,6 +2297,7 @@ merge(Compressor.prototype, {
switch (this.operator) {
case "&&":
case "||":
if (right === this.right) return this;
var node = this.clone();
node.right = right;
return node;
@@ -2299,26 +2331,19 @@ merge(Compressor.prototype, {
return node;
});
def(AST_Unary, function(compressor, first_in_statement){
switch (this.operator) {
case "delete":
case "++":
case "--":
return this;
case "typeof":
if (this.expression instanceof AST_SymbolRef) return null;
default:
var expression = this.expression.drop_side_effect_free(compressor, first_in_statement);
if (first_in_statement
&& this instanceof AST_UnaryPrefix
&& is_iife_call(expression)) {
if (expression === this.expression && this.operator.length === 1) return this;
return make_node(AST_UnaryPrefix, this, {
operator: this.operator.length === 1 ? this.operator : "!",
expression: expression
});
}
return expression;
if (unary_side_effects(this.operator)) return this;
if (this.operator == "typeof" && this.expression instanceof AST_SymbolRef) return null;
var expression = this.expression.drop_side_effect_free(compressor, first_in_statement);
if (first_in_statement
&& this instanceof AST_UnaryPrefix
&& is_iife_call(expression)) {
if (expression === this.expression && this.operator.length === 1) return this;
return make_node(AST_UnaryPrefix, this, {
operator: this.operator.length === 1 ? this.operator : "!",
expression: expression
});
}
return expression;
});
def(AST_SymbolRef, function() {
return this.undeclared() ? this : null;
@@ -2526,8 +2551,8 @@ merge(Compressor.prototype, {
return make_node(AST_SimpleStatement, self, {
body: make_node(AST_Conditional, self, {
condition : self.condition,
consequent : statement_to_expression(self.body),
alternative : statement_to_expression(self.alternative)
consequent : self.body.body,
alternative : self.alternative.body
})
}).optimize(compressor);
}
@@ -2543,25 +2568,24 @@ merge(Compressor.prototype, {
body: make_node(AST_Binary, self, {
operator : "||",
left : negated,
right : statement_to_expression(self.body)
right : self.body.body
})
}).optimize(compressor);
return make_node(AST_SimpleStatement, self, {
body: make_node(AST_Binary, self, {
operator : "&&",
left : self.condition,
right : statement_to_expression(self.body)
right : self.body.body
})
}).optimize(compressor);
}
if (self.body instanceof AST_EmptyStatement
&& self.alternative
&& self.alternative instanceof AST_SimpleStatement) {
return make_node(AST_SimpleStatement, self, {
body: make_node(AST_Binary, self, {
operator : "||",
left : self.condition,
right : statement_to_expression(self.alternative)
right : self.alternative.body
})
}).optimize(compressor);
}
@@ -2611,105 +2635,93 @@ merge(Compressor.prototype, {
});
OPT(AST_Switch, function(self, compressor){
if (self.body.length == 0 && compressor.option("conditionals")) {
return make_node(AST_SimpleStatement, self, {
body: self.expression
}).transform(compressor);
var branch;
var value = self.expression.evaluate(compressor);
if (value !== self.expression) {
var expression = make_node_from_constant(value, self.expression).transform(compressor);
self.expression = best_of_expression(expression, self.expression);
}
for(;;) {
var last_branch = self.body[self.body.length - 1];
if (last_branch) {
var stat = last_branch.body[last_branch.body.length - 1]; // last statement
if (stat instanceof AST_Break && loop_body(compressor.loopcontrol_target(stat.label)) === self)
last_branch.body.pop();
if (last_branch instanceof AST_Default && last_branch.body.length == 0) {
self.body.pop();
if (!compressor.option("dead_code")) return self;
var prev_block;
var decl = [];
var body = [];
var default_branch;
var exact_match;
var fallthrough;
for (var i = 0, len = self.body.length; i < len && !exact_match; i++) {
branch = self.body[i];
if (branch instanceof AST_Default) {
if (!default_branch) default_branch = branch;
else if (!fallthrough) {
extract_declarations_from_unreachable_code(compressor, branch, decl);
continue;
}
} else if (value !== self.expression) {
var exp = branch.expression.evaluate(compressor);
if (exp === value) {
exact_match = branch;
if (default_branch) {
body.splice(body.indexOf(default_branch), 1);
extract_declarations_from_unreachable_code(compressor, default_branch, decl);
default_branch = null;
}
} else if (exp !== branch.expression && !fallthrough) {
extract_declarations_from_unreachable_code(compressor, branch, decl);
continue;
}
}
break;
if (aborts(branch)) {
var block = make_node(AST_BlockStatement, branch, branch).print_to_string();
if (!fallthrough && prev_block === block) body[body.length - 1].body = [];
body.push(branch);
prev_block = block;
fallthrough = false;
} else {
body.push(branch);
prev_block = null;
fallthrough = true;
}
}
var value = self.expression.evaluate(compressor);
out: if (value !== self.expression) try {
// constant expression
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 in_if = false;
var in_block = false;
var started = false;
var stopped = false;
var ruined = false;
var tt = new TreeTransformer(function(node, descend, in_list){
if (node instanceof AST_Lambda || node instanceof AST_SimpleStatement) {
// no need to descend these node types
return node;
}
else if (node instanceof AST_Switch && node === self) {
node = node.clone();
descend(node, this);
return ruined ? node : make_node(AST_BlockStatement, node, {
body: node.body.reduce(function(a, branch){
return a.concat(branch.body);
}, [])
}).transform(compressor);
}
else if (node instanceof AST_If || node instanceof AST_Try) {
var save = in_if;
in_if = !in_block;
descend(node, this);
in_if = save;
return node;
}
else if (node instanceof AST_StatementWithBody || node instanceof AST_Switch) {
var save = in_block;
in_block = true;
descend(node, this);
in_block = save;
return node;
}
else if (node instanceof AST_Break && this.loopcontrol_target(node.label) === self) {
if (in_if) {
ruined = true;
return node;
}
if (in_block) return node;
stopped = true;
return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
}
else if (node instanceof AST_SwitchBranch && this.parent() === self) {
if (stopped) return MAP.skip;
if (node instanceof AST_Case) {
var exp = node.expression.evaluate(compressor);
if (exp === node.expression) {
// got a case with non-constant expression, baling out
throw self;
}
if (exp === value || started) {
started = true;
if (aborts(node)) stopped = true;
descend(node, this);
return node;
}
return MAP.skip;
}
descend(node, this);
return node;
}
for (; i < len && fallthrough; i++) {
branch = self.body[i];
exact_match.body = exact_match.body.concat(branch.body);
fallthrough = !aborts(exact_match);
}
while (i < len) extract_declarations_from_unreachable_code(compressor, self.body[i++], decl);
if (body.length > 0) {
body[0].body = decl.concat(body[0].body);
}
self.body = body;
while (branch = body[body.length - 1]) {
var stat = branch.body[branch.body.length - 1];
if (stat instanceof AST_Break && compressor.loopcontrol_target(stat.label) === self)
branch.body.pop();
if (branch.body.length || branch instanceof AST_Case
&& (default_branch || branch.expression.has_side_effects(compressor))) break;
if (body.pop() === default_branch) default_branch = null;
}
if (body.length == 0) {
return make_node(AST_BlockStatement, self, {
body: decl.concat(make_node(AST_SimpleStatement, self.expression, {
body: self.expression
}))
}).optimize(compressor);
}
if (body.length == 1 && (body[0] === exact_match || body[0] === default_branch)) {
var has_break = false;
var tw = new TreeWalker(function(node) {
if (has_break
|| node instanceof AST_Lambda
|| node instanceof AST_SimpleStatement) return true;
if (node instanceof AST_Break && tw.loopcontrol_target(node.label) === self)
has_break = true;
});
tt.stack = compressor.stack.slice(); // so that's able to see parent nodes
self = self.transform(tt);
} catch(ex) {
if (ex !== self) throw ex;
self.walk(tw);
if (!has_break) return make_node(AST_BlockStatement, self, body[0]).optimize(compressor);
}
return self;
});
OPT(AST_Case, function(self, compressor){
self.body = tighten_body(self.body, compressor);
return self;
});
OPT(AST_Try, function(self, compressor){
self.body = tighten_body(self.body, compressor);
return self;
@@ -3056,7 +3068,9 @@ merge(Compressor.prototype, {
&& (self.car.operator == "++" || self.car.operator == "--")) {
left = self.car.expression;
}
if (left) {
if (left
&& !(left instanceof AST_SymbolRef
&& left.definition().orig[0] instanceof AST_SymbolLambda)) {
var parent, field;
var cdr = self.cdr;
while (true) {
@@ -3072,9 +3086,14 @@ merge(Compressor.prototype, {
return car;
}
if (cdr instanceof AST_Binary && !(cdr instanceof AST_Assign)) {
field = cdr.left.is_constant() ? "right" : "left";
if (cdr.left.is_constant()) {
if (cdr.operator == "||" || cdr.operator == "&&") break;
field = "right";
} else {
field = "left";
}
} else if (cdr instanceof AST_Call
|| cdr instanceof AST_Unary && cdr.operator != "++" && cdr.operator != "--") {
|| cdr instanceof AST_Unary && !unary_side_effects(cdr.operator)) {
field = "expression";
} else break;
parent = cdr;
@@ -3139,10 +3158,10 @@ merge(Compressor.prototype, {
// typeof always returns a non-empty string, thus it's
// always true in booleans
compressor.warn("Boolean expression always true [{file}:{line},{col}]", self.start);
return make_node(AST_Seq, self, {
return (e instanceof AST_SymbolRef ? make_node(AST_True, self) : make_node(AST_Seq, self, {
car: e,
cdr: make_node(AST_True, self)
}).optimize(compressor);
})).optimize(compressor);
}
}
// avoids infinite recursion of numerals
@@ -3424,6 +3443,7 @@ merge(Compressor.prototype, {
left: self.left,
right: self.right.expression
});
break;
}
// -a + b => b - a
if (self.left instanceof AST_UnaryPrefix
@@ -3435,6 +3455,7 @@ merge(Compressor.prototype, {
left: self.right,
right: self.left.expression
});
break;
}
case "*":
associative = compressor.option("unsafe_math");
@@ -3698,21 +3719,30 @@ merge(Compressor.prototype, {
alternative: self.consequent
});
}
var condition = self.condition;
var consequent = self.consequent;
var alternative = self.alternative;
// x?x:y --> x||y
if (condition instanceof AST_SymbolRef
&& consequent instanceof AST_SymbolRef
&& condition.definition() === consequent.definition()) {
return make_node(AST_Binary, self, {
operator: "||",
left: condition,
right: alternative
});
}
// if (foo) exp = something; else exp = something_else;
// |
// v
// exp = foo ? something : something_else;
if (consequent instanceof AST_Assign
&& alternative instanceof AST_Assign
&& consequent.operator == alternative.operator
&& consequent.left.equivalent_to(alternative.left)
&& (!consequent.left.has_side_effects(compressor)
|| !self.condition.has_side_effects(compressor))
) {
/*
* Stuff like this:
* if (foo) exp = something; else exp = something_else;
* ==>
* exp = foo ? something : something_else;
*/
&& (!self.condition.has_side_effects(compressor)
|| consequent.operator == "="
&& !consequent.left.has_side_effects(compressor))) {
return make_node(AST_Assign, self, {
operator: consequent.operator,
left: consequent.left,

View File

@@ -904,7 +904,7 @@ function OutputStream(options) {
output.print("for");
output.space();
output.with_parens(function(){
if (self.init && !(self.init instanceof AST_EmptyStatement)) {
if (self.init) {
if (self.init instanceof AST_Definitions) {
self.init.print(output);
} else {

View File

@@ -852,6 +852,7 @@ function parse($TEXT, options) {
html5_comments : true,
bare_returns : false,
shebang : true,
cli : false,
});
var S = {
@@ -2418,7 +2419,7 @@ function parse($TEXT, options) {
function make_unary(ctor, op, expr) {
if ((op == "++" || op == "--") && !is_assignable(expr))
croak("Invalid use of " + op + " operator");
croak("Invalid use of " + op + " operator", null, ctor === AST_UnaryPrefix ? expr.start.col - 1 : null);
return new ctor({ operator: op, expression: expr });
};
@@ -2467,6 +2468,7 @@ function parse($TEXT, options) {
};
function is_assignable(expr) {
if (options.cli) return true;
return expr instanceof AST_PropAccess || expr instanceof AST_SymbolRef;
};

View File

@@ -83,9 +83,16 @@ SymbolDef.prototype = {
}
else if (!this.mangled_name && !this.unmangleable(options)) {
var s = this.scope;
if (!options.screw_ie8 && this.orig[0] instanceof AST_SymbolLambda)
var sym = this.orig[0];
if (!options.screw_ie8 && sym instanceof AST_SymbolLambda)
s = s.parent_scope;
this.mangled_name = s.next_mangled(options, this);
var def;
if (options.screw_ie8
&& sym instanceof AST_SymbolCatch
&& (def = s.parent_scope.find_variable(sym))) {
this.mangled_name = def.mangled_name || def.name;
} else
this.mangled_name = s.next_mangled(options, this);
if (this.global && cache) {
cache.set(this.name, this.mangled_name);
}
@@ -111,8 +118,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
if (node.is_block_scope()) {
var save_scope = scope;
scope = new AST_Scope(node);
scope.init_scope_vars();
scope.parent_scope = save_scope;
scope.init_scope_vars(save_scope);
if (!(node instanceof AST_Scope)) {
scope.uses_with = save_scope.uses_with;
scope.uses_eval = save_scope.uses_eval;
@@ -129,8 +135,8 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
return true;
}
if (node instanceof AST_Scope) {
node.init_scope_vars();
var save_scope = node.parent_scope = scope;
node.init_scope_vars(scope);
var save_scope = scope;
var save_defun = defun;
var save_labels = labels;
defun = scope = node;
@@ -213,6 +219,14 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
|| node instanceof AST_SymbolConst) {
var def = ((node instanceof AST_SymbolBlockDeclaration) ? scope : defun).def_variable(node, in_export, in_block);
def.destructuring = in_destructuring;
if (defun !== scope) {
node.mark_enclosed(options);
var def = scope.find_variable(node);
if (node.thedef !== def) {
node.thedef = def;
node.reference(options);
}
}
}
else if (node instanceof AST_SymbolCatch) {
scope.def_variable(node, in_export, in_block);
@@ -309,14 +323,14 @@ AST_Toplevel.DEFMETHOD("def_global", function(node){
}
});
AST_Scope.DEFMETHOD("init_scope_vars", function(){
this.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions)
this.functions = new Dictionary(); // map name to AST_SymbolDefun (functions defined in this scope)
this.uses_with = false; // will be set to true if this or some nested scope uses the `with` statement
this.uses_eval = false; // will be set to true if this or nested scope uses the global `eval`
this.parent_scope = null; // the parent scope
this.enclosed = []; // a list of variables from this or outer scope(s) that are referenced from this or inner scopes
this.cname = -1; // the current index for mangling functions/variables
AST_Scope.DEFMETHOD("init_scope_vars", function(parent_scope){
this.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions)
this.functions = new Dictionary(); // map name to AST_SymbolDefun (functions defined in this scope)
this.uses_with = false; // will be set to true if this or some nested scope uses the `with` statement
this.uses_eval = false; // will be set to true if this or nested scope uses the global `eval`
this.parent_scope = parent_scope; // the parent scope
this.enclosed = []; // a list of variables from this or outer scope(s) that are referenced from this or inner scopes
this.cname = -1; // the current index for mangling functions/variables
});
AST_Node.DEFMETHOD("is_block_scope", function(){
@@ -339,15 +353,15 @@ AST_IterationStatement.DEFMETHOD("is_block_scope", function(){
AST_Lambda.DEFMETHOD("init_scope_vars", function(){
AST_Scope.prototype.init_scope_vars.apply(this, arguments);
this.uses_arguments = false;
var symbol = new AST_VarDef({ name: "arguments", start: this.start, end: this.end });
var def = new SymbolDef(this, this.variables.size(), symbol);
this.variables.set(symbol.name, def);
this.def_variable(new AST_SymbolVar({
name: "arguments",
start: this.start,
end: this.end
}));
});
AST_SymbolRef.DEFMETHOD("reference", function(options) {
AST_Symbol.DEFMETHOD("mark_enclosed", function(options) {
var def = this.definition();
def.references.push(this);
var s = this.scope;
while (s) {
push_uniq(s.enclosed, def);
@@ -361,6 +375,11 @@ AST_SymbolRef.DEFMETHOD("reference", function(options) {
}
});
AST_Symbol.DEFMETHOD("reference", function(options) {
this.definition().references.push(this);
this.mark_enclosed(options);
});
AST_Scope.DEFMETHOD("find_variable", function(name){
if (name instanceof AST_Symbol) name = name.name;
return this.variables.get(name)

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

View File

@@ -894,7 +894,8 @@ collapse_vars_unary: {
}
expect: {
function f0(o, p) {
delete o[p];
var x = o[p];
delete x;
}
function f1(n) {
return n > +!!n
@@ -1621,3 +1622,71 @@ issue_1631_3: {
}
expect_stdout: "6"
}
var_side_effects_1: {
options = {
collapse_vars: true,
}
input: {
var print = console.log.bind(console);
function foo(x) {
var twice = x * 2;
print('Foo:', twice);
}
foo(10);
}
expect: {
var print = console.log.bind(console);
function foo(x) {
print('Foo:', 2 * x);
}
foo(10);
}
expect_stdout: true
}
var_side_effects_2: {
options = {
collapse_vars: true,
}
input: {
var print = console.log.bind(console);
function foo(x) {
var twice = x.y * 2;
print('Foo:', twice);
}
foo({ y: 10 });
}
expect: {
var print = console.log.bind(console);
function foo(x) {
var twice = 2 * x.y;
print('Foo:', twice);
}
foo({ y: 10 });
}
expect_stdout: true
}
var_side_effects_3: {
options = {
collapse_vars: true,
pure_getters: true,
}
input: {
var print = console.log.bind(console);
function foo(x) {
var twice = x.y * 2;
print('Foo:', twice);
}
foo({ y: 10 });
}
expect: {
var print = console.log.bind(console);
function foo(x) {
print('Foo:', 2 * x.y);
}
foo({ y: 10 });
}
expect_stdout: true
}

View File

@@ -893,3 +893,72 @@ equality_conditionals_true: {
}
expect_stdout: true
}
issue_1645_1: {
options = {
conditionals: true,
}
input: {
var a = 100, b = 10;
(b = a) ? a++ + (b += a) ? b += a : b += a : b ^= a;
console.log(a, b);
}
expect: {
var a = 100, b = 10;
(b = a) ? (a++ + (b += a), b += a) : b ^= a;
console.log(a,b);
}
expect_stdout: true
}
issue_1645_2: {
options = {
conditionals: true,
}
input: {
var a = 0;
function f() {
return a++;
}
f() ? a += 2 : a += 4;
console.log(a);
}
expect: {
var a = 0;
function f(){
return a++;
}
f() ? a += 2 : a += 4;
console.log(a);
}
expect_stdout: true
}
condition_symbol_matches_consequent: {
options = {
conditionals: true,
}
input: {
function foo(x, y) {
return x ? x : y;
}
function bar() {
return g ? g : h;
}
var g = 4;
var h = 5;
console.log(foo(3, null), foo(0, 7), foo(true, false), bar());
}
expect: {
function foo(x, y) {
return x || y;
}
function bar() {
return g || h;
}
var g = 4;
var h = 5;
console.log(foo(3, null), foo(0, 7), foo(true, false), bar());
}
expect_stdout: "3 7 true 4"
}

View File

@@ -872,3 +872,143 @@ issue_1583: {
}
}
}
issue_1656: {
options = {
toplevel: true,
unused: true,
}
beautify = {
beautify: true,
}
input: {
for(var a=0;;);
}
expect_exact: "for (;;) ;"
}
issue_1709: {
options = {
unused: true,
}
input: {
console.log(
function x() {
var x = 1;
return x;
}(),
function y() {
const y = 2;
return y;
}(),
function z() {
function z() {}
return z;
}()
);
}
expect: {
console.log(
function() {
var x = 1;
return x;
}(),
function() {
const y = 2;
return y;
}(),
function() {
function z() {}
return z;
}()
);
}
expect_stdout: true
}
issue_1715_1: {
options = {
unused: true,
}
input: {
var a = 1;
function f() {
a++;
try {} catch (a) {
var a;
}
}
f();
console.log(a);
}
expect: {
var a = 1;
function f() {
a++;
try {} catch (a) {
var a;
}
}
f();
console.log(a);
}
expect_stdout: "1"
}
issue_1715_2: {
options = {
unused: true,
}
input: {
var a = 1;
function f() {
a++;
try {} catch (a) {
var a = 2;
}
}
f();
console.log(a);
}
expect: {
var a = 1;
function f() {
a++;
try {} catch (a) {
var a;
}
}
f();
console.log(a);
}
expect_stdout: "1"
}
issue_1715_3: {
options = {
unused: true,
}
input: {
var a = 1;
function f() {
a++;
try {} catch (a) {
var a = 2 + x();
}
}
f();
console.log(a);
}
expect: {
var a = 1;
function f() {
a++;
try {} catch (a) {
var a = x();
}
}
f();
console.log(a);
}
expect_stdout: "1"
}

View File

@@ -883,3 +883,16 @@ unsafe_charAt_noop: {
);
}
}
issue_1649: {
options = {
evaluate: true,
}
input: {
console.log(-1 + -1);
}
expect: {
console.log(-2);
}
expect_stdout: "-2";
}

View File

@@ -0,0 +1,88 @@
issue_1639_1: {
options = {
booleans: true,
cascade: true,
conditionals: true,
evaluate: true,
join_vars: true,
loops: true,
sequences: true,
side_effects: true,
}
input: {
var a = 100, b = 10;
var L1 = 5;
while (--L1 > 0) {
if ((--b), false) {
if (b) {
var ignore = 0;
}
}
}
console.log(a, b);
}
expect: {
for (var a = 100, b = 10, L1 = 5; --L1 > 0;)
if (--b, !1) var ignore = 0;
console.log(a, b);
}
expect_stdout: true
}
issue_1639_2: {
options = {
booleans: true,
cascade: true,
conditionals: true,
evaluate: true,
join_vars: true,
sequences: true,
side_effects: true,
}
input: {
var a = 100, b = 10;
function f19() {
if (++a, false)
if (a)
if (++a);
}
f19();
console.log(a, b);
}
expect: {
var a = 100, b = 10;
function f19() {
++a, 1;
}
f19(),
console.log(a, b);
}
expect_stdout: true
}
issue_1639_3: {
options = {
booleans: true,
cascade: true,
conditionals: true,
evaluate: true,
sequences: true,
side_effects: true,
}
input: {
var a = 100, b = 10;
a++ && false && a ? 0 : 0;
console.log(a, b);
}
expect: {
var a = 100, b = 10;
a++,
console.log(a, b);
}
expect_stdout: true
}

View File

@@ -0,0 +1,45 @@
f7: {
options = {
booleans: true,
cascade: true,
collapse_vars: true,
comparisons: true,
conditionals: true,
dead_code: true,
drop_debugger: true,
evaluate: true,
hoist_funs: true,
if_return: true,
join_vars: true,
loops: true,
negate_iife: true,
passes: 3,
properties: true,
reduce_vars: true,
sequences: true,
side_effects: true,
toplevel: true,
unused: true,
}
beautify = {
beautify: true,
}
input: {
var a = 100, b = 10;
function f22464() {
var brake146670 = 5;
while (((b = a) ? !a : ~a ? null : b += a) && --brake146670 > 0) {
}
}
f22464();
console.log(a, b);
}
expect_exact: [
"var a = 100, b = 10;",
"",
"!function() {",
" for (;b = a, !1; ) ;",
"}(), console.log(a, b);",
]
expect_stdout: true
}

159
test/compress/issue-1673.js Normal file
View File

@@ -0,0 +1,159 @@
side_effects_catch: {
options = {
reduce_vars: true,
side_effects: true,
unused: true,
}
input: {
function f() {
function g() {
try {
throw 0;
} catch (e) {
console.log("PASS");
}
}
g();
}
f();
}
expect: {
function f() {
(function() {
try {
throw 0;
} catch (e) {
console.log("PASS");
}
})();
}
f();
}
expect_stdout: "PASS"
}
side_effects_else: {
options = {
reduce_vars: true,
side_effects: true,
unused: true,
}
input: {
function f(x) {
function g() {
if (x);
else console.log("PASS");
}
g();
}
f(0);
}
expect: {
function f(x) {
(function() {
if (x);
else console.log("PASS");
})();
}
f(0);
}
expect_stdout: "PASS"
}
side_effects_finally: {
options = {
reduce_vars: true,
side_effects: true,
unused: true,
}
input: {
function f() {
function g() {
try {
} catch (e) {
} finally {
console.log("PASS");
}
}
g();
}
f();
}
expect: {
function f() {
(function() {
try {
} catch (e) {
} finally {
console.log("PASS");
}
})();
}
f();
}
expect_stdout: "PASS"
}
side_effects_label: {
options = {
reduce_vars: true,
side_effects: true,
unused: true,
}
input: {
function f(x) {
function g() {
L: {
console.log("PASS");
break L;
}
}
g();
}
f(0);
}
expect: {
function f(x) {
(function() {
L: {
console.log("PASS");
break L;
}
})();
}
f(0);
}
expect_stdout: "PASS"
}
side_effects_switch: {
options = {
reduce_vars: true,
side_effects: true,
unused: true,
}
input: {
function f() {
function g() {
switch (0) {
default:
case console.log("PASS"):
}
}
g();
}
f();
}
expect: {
function f() {
(function() {
switch (0) {
default:
case console.log("PASS"):
}
})();
}
f();
}
expect_stdout: "PASS"
}

347
test/compress/issue-1704.js Normal file
View File

@@ -0,0 +1,347 @@
mangle_catch: {
options = {
screw_ie8: true,
toplevel: false,
}
mangle = {
screw_ie8: true,
toplevel: false,
}
input: {
var a = "FAIL";
try {
throw 1;
} catch (args) {
a = "PASS";
}
console.log(a);
}
expect_exact: 'var a="FAIL";try{throw 1}catch(o){a="PASS"}console.log(a);'
expect_stdout: "PASS"
}
mangle_catch_ie8: {
options = {
screw_ie8: false,
toplevel: false,
}
mangle = {
screw_ie8: false,
toplevel: false,
}
input: {
var a = "FAIL";
try {
throw 1;
} catch (args) {
a = "PASS";
}
console.log(a);
}
expect_exact: 'var a="FAIL";try{throw 1}catch(args){a="PASS"}console.log(a);'
expect_stdout: "PASS"
}
mangle_catch_var: {
options = {
screw_ie8: true,
toplevel: false,
}
mangle = {
screw_ie8: true,
toplevel: false,
}
input: {
var a = "FAIL";
try {
throw 1;
} catch (args) {
var a = "PASS";
}
console.log(a);
}
expect_exact: 'var a="FAIL";try{throw 1}catch(o){var a="PASS"}console.log(a);'
expect_stdout: "PASS"
}
mangle_catch_var_ie8: {
options = {
screw_ie8: false,
toplevel: false,
}
mangle = {
screw_ie8: false,
toplevel: false,
}
input: {
var a = "FAIL";
try {
throw 1;
} catch (args) {
var a = "PASS";
}
console.log(a);
}
expect_exact: 'var a="FAIL";try{throw 1}catch(args){var a="PASS"}console.log(a);'
expect_stdout: "PASS"
}
mangle_catch_toplevel: {
options = {
screw_ie8: true,
toplevel: true,
}
mangle = {
screw_ie8: true,
toplevel: true,
}
input: {
var a = "FAIL";
try {
throw 1;
} catch (args) {
a = "PASS";
}
console.log(a);
}
expect_exact: 'var o="FAIL";try{throw 1}catch(c){o="PASS"}console.log(o);'
expect_stdout: "PASS"
}
mangle_catch_ie8_toplevel: {
options = {
screw_ie8: false,
toplevel: true,
}
mangle = {
screw_ie8: false,
toplevel: true,
}
input: {
var a = "FAIL";
try {
throw 1;
} catch (args) {
a = "PASS";
}
console.log(a);
}
expect_exact: 'var o="FAIL";try{throw 1}catch(c){o="PASS"}console.log(o);'
expect_stdout: "PASS"
}
mangle_catch_var_toplevel: {
options = {
screw_ie8: true,
toplevel: true,
}
mangle = {
screw_ie8: true,
toplevel: true,
}
input: {
var a = "FAIL";
try {
throw 1;
} catch (args) {
var a = "PASS";
}
console.log(a);
}
expect_exact: 'var o="FAIL";try{throw 1}catch(r){var o="PASS"}console.log(o);'
expect_stdout: "PASS"
}
mangle_catch_var_ie8_toplevel: {
options = {
screw_ie8: false,
toplevel: true,
}
mangle = {
screw_ie8: false,
toplevel: true,
}
input: {
var a = "FAIL";
try {
throw 1;
} catch (args) {
var a = "PASS";
}
console.log(a);
}
expect_exact: 'var o="FAIL";try{throw 1}catch(r){var o="PASS"}console.log(o);'
expect_stdout: "PASS"
}
mangle_catch_redef_1: {
options = {
screw_ie8: true,
toplevel: false,
}
mangle = {
screw_ie8: true,
toplevel: false,
}
input: {
var a = "PASS";
try {
throw "FAIL1";
} catch (a) {
var a = "FAIL2";
}
console.log(a);
}
expect_exact: 'var a="PASS";try{throw"FAIL1"}catch(a){var a="FAIL2"}console.log(a);'
expect_stdout: "PASS"
}
mangle_catch_redef_1_ie8: {
options = {
screw_ie8: false,
toplevel: false,
}
mangle = {
screw_ie8: false,
toplevel: false,
}
input: {
var a = "PASS";
try {
throw "FAIL1";
} catch (a) {
var a = "FAIL2";
}
console.log(a);
}
expect_exact: 'var a="PASS";try{throw"FAIL1"}catch(a){var a="FAIL2"}console.log(a);'
expect_stdout: "PASS"
}
mangle_catch_redef_1_toplevel: {
options = {
screw_ie8: true,
toplevel: true,
}
mangle = {
screw_ie8: true,
toplevel: true,
}
input: {
var a = "PASS";
try {
throw "FAIL1";
} catch (a) {
var a = "FAIL2";
}
console.log(a);
}
expect_exact: 'var o="PASS";try{throw"FAIL1"}catch(o){var o="FAIL2"}console.log(o);'
expect_stdout: "PASS"
}
mangle_catch_redef_1_ie8_toplevel: {
options = {
screw_ie8: false,
toplevel: true,
}
mangle = {
screw_ie8: false,
toplevel: true,
}
input: {
var a = "PASS";
try {
throw "FAIL1";
} catch (a) {
var a = "FAIL2";
}
console.log(a);
}
expect_exact: 'var o="PASS";try{throw"FAIL1"}catch(o){var o="FAIL2"}console.log(o);'
expect_stdout: "PASS"
}
mangle_catch_redef_2: {
options = {
screw_ie8: true,
toplevel: false,
}
mangle = {
screw_ie8: true,
toplevel: false,
}
input: {
try {
throw "FAIL1";
} catch (a) {
var a = "FAIL2";
}
console.log(a);
}
expect_exact: 'try{throw"FAIL1"}catch(a){var a="FAIL2"}console.log(a);'
expect_stdout: "undefined"
}
mangle_catch_redef_2_ie8: {
options = {
screw_ie8: false,
toplevel: false,
}
mangle = {
screw_ie8: false,
toplevel: false,
}
input: {
try {
throw "FAIL1";
} catch (a) {
var a = "FAIL2";
}
console.log(a);
}
expect_exact: 'try{throw"FAIL1"}catch(a){var a="FAIL2"}console.log(a);'
expect_stdout: "undefined"
}
mangle_catch_redef_2_toplevel: {
options = {
screw_ie8: true,
toplevel: true,
}
mangle = {
screw_ie8: true,
toplevel: true,
}
input: {
try {
throw "FAIL1";
} catch (a) {
var a = "FAIL2";
}
console.log(a);
}
expect_exact: 'try{throw"FAIL1"}catch(o){var o="FAIL2"}console.log(o);'
expect_stdout: "undefined"
}
mangle_catch_redef_2_ie8_toplevel: {
options = {
screw_ie8: false,
toplevel: true,
}
mangle = {
screw_ie8: false,
toplevel: true,
}
input: {
try {
throw "FAIL1";
} catch (a) {
var a = "FAIL2";
}
console.log(a);
}
expect_exact: 'try{throw"FAIL1"}catch(o){var o="FAIL2"}console.log(o);'
expect_stdout: "undefined"
}

View File

@@ -440,3 +440,21 @@ issue_186_beautify_bracketize_ie8: {
'}',
]
}
issue_1648: {
options = {
join_vars: true,
loops: true,
passes: 2,
sequences: true,
unused: true,
}
input: {
function f() {
x();
var b = 1;
while (1);
}
}
expect_exact: "function f(){for(x();1;);}"
}

View File

@@ -353,8 +353,9 @@ issue_1254_negate_iife_nested: {
issue_1288: {
options = {
negate_iife: true,
conditionals: true,
negate_iife: true,
side_effects: false,
};
input: {
if (w) ;
@@ -374,11 +375,11 @@ issue_1288: {
})(0);
}
expect: {
w || function f() {}();
x || function() {
w || !function f() {}();
x || !function() {
x = {};
}();
y ? function() {}() : function(z) {
y ? !function() {}() : !function(z) {
return z;
}(0);
}

View File

@@ -153,3 +153,18 @@ evaluate_4: {
);
}
}
issue_1710: {
options = {
evaluate: true,
}
input: {
var x = {};
console.log((x += 1) + -x);
}
expect: {
var x = {};
console.log((x += 1) + -x);
}
expect_stdout: true
}

View File

@@ -425,7 +425,7 @@ iife_new: {
expect_stdout: true
}
multi_def: {
multi_def_1: {
options = {
evaluate: true,
reduce_vars: true,
@@ -435,7 +435,7 @@ multi_def: {
if (a)
var b = 1;
else
var b = 2
var b = 2;
console.log(b + 1);
}
}
@@ -444,7 +444,7 @@ multi_def: {
if (a)
var b = 1;
else
var b = 2
var b = 2;
console.log(b + 1);
}
}
@@ -479,6 +479,33 @@ multi_def_2: {
}
}
multi_def_3: {
options = {
evaluate: true,
reduce_vars: true,
}
input: {
function f(a) {
var b = 2;
if (a)
var b;
else
var b;
console.log(b + 1);
}
}
expect: {
function f(a) {
var b = 2;
if (a)
var b;
else
var b;
console.log(3);
}
}
}
use_before_var: {
options = {
evaluate: true,
@@ -1364,3 +1391,466 @@ issue_1606: {
}
}
}
issue_1670_1: {
options = {
comparisons: true,
conditionals: true,
evaluate: true,
dead_code: true,
reduce_vars: true,
unused: true,
}
input: {
(function f() {
switch (1) {
case 0:
var a = true;
break;
default:
if (typeof a === "undefined") console.log("PASS");
else console.log("FAIL");
}
})();
}
expect: {
(function() {
var a;
void 0 === a ? console.log("PASS") : console.log("FAIL");
})();
}
expect_stdout: "PASS"
}
issue_1670_2: {
options = {
conditionals: true,
evaluate: true,
dead_code: true,
passes: 2,
reduce_vars: true,
unused: true,
}
input: {
(function f() {
switch (1) {
case 0:
var a = true;
break;
default:
if (typeof a === "undefined") console.log("PASS");
else console.log("FAIL");
}
})();
}
expect: {
(function() {
console.log("PASS");
})();
}
expect_stdout: "PASS"
}
issue_1670_3: {
options = {
comparisons: true,
conditionals: true,
evaluate: true,
dead_code: true,
reduce_vars: true,
unused: true,
}
input: {
(function f() {
switch (1) {
case 0:
var a = true;
break;
case 1:
if (typeof a === "undefined") console.log("PASS");
else console.log("FAIL");
}
})();
}
expect: {
(function() {
var a;
void 0 === a ? console.log("PASS") : console.log("FAIL");
})();
}
expect_stdout: "PASS"
}
issue_1670_4: {
options = {
conditionals: true,
evaluate: true,
dead_code: true,
passes: 2,
reduce_vars: true,
unused: true,
}
input: {
(function f() {
switch (1) {
case 0:
var a = true;
break;
case 1:
if (typeof a === "undefined") console.log("PASS");
else console.log("FAIL");
}
})();
}
expect: {
(function() {
console.log("PASS");
})();
}
expect_stdout: "PASS"
}
issue_1670_5: {
options = {
dead_code: true,
evaluate: true,
keep_fargs: false,
reduce_vars: true,
unused: true,
}
input: {
(function(a) {
switch (1) {
case a:
console.log(a);
break;
default:
console.log(2);
break;
}
})(1);
}
expect: {
(function() {
console.log(1);
})();
}
expect_stdout: "1"
}
issue_1670_6: {
options = {
dead_code: true,
evaluate: true,
keep_fargs: false,
reduce_vars: true,
unused: true,
}
input: {
(function(a) {
switch (1) {
case a = 1:
console.log(a);
break;
default:
console.log(2);
break;
}
})(1);
}
expect: {
(function(a) {
switch (1) {
case a = 1:
console.log(a);
break;
default:
console.log(2);
}
})(1);
}
expect_stdout: "1"
}
unary_delete: {
options = {
evaluate: true,
reduce_vars: true,
unused: true,
}
input: {
var b = 10;
function f() {
var a;
if (delete a) b--;
}
f();
console.log(b);
}
expect: {
var b = 10;
function f() {
var a;
if (delete a) b--;
}
f();
console.log(b);
}
expect_stdout: true
}
redefine_arguments_1: {
options = {
evaluate: true,
keep_fargs: false,
reduce_vars: true,
unused: true,
}
input: {
function f() {
var arguments;
return typeof arguments;
}
function g() {
var arguments = 42;
return typeof arguments;
}
function h(x) {
var arguments = x;
return typeof arguments;
}
console.log(f(), g(), h());
}
expect: {
function f() {
var arguments;
return typeof arguments;
}
function g() {
return"number";
}
function h(x) {
var arguments = x;
return typeof arguments;
}
console.log(f(), g(), h());
}
expect_stdout: "object number undefined"
}
redefine_arguments_2: {
options = {
evaluate: true,
keep_fargs: false,
reduce_vars: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
function f() {
var arguments;
return typeof arguments;
}
function g() {
var arguments = 42;
return typeof arguments;
}
function h(x) {
var arguments = x;
return typeof arguments;
}
console.log(f(), g(), h());
}
expect: {
console.log(function() {
var arguments;
return typeof arguments;
}(), function() {
return"number";
}(), function(x) {
var arguments = x;
return typeof arguments;
}());
}
expect_stdout: "object number undefined"
}
redefine_arguments_3: {
options = {
evaluate: true,
keep_fargs: false,
passes: 3,
reduce_vars: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
function f() {
var arguments;
return typeof arguments;
}
function g() {
var arguments = 42;
return typeof arguments;
}
function h(x) {
var arguments = x;
return typeof arguments;
}
console.log(f(), g(), h());
}
expect: {
console.log(function() {
var arguments;
return typeof arguments;
}(), "number", "undefined");
}
expect_stdout: "object number undefined"
}
redefine_farg_1: {
options = {
evaluate: true,
keep_fargs: false,
reduce_vars: true,
unused: true,
}
input: {
function f(a) {
var a;
return typeof a;
}
function g(a) {
var a = 42;
return typeof a;
}
function h(a, b) {
var a = b;
return typeof a;
}
console.log(f([]), g([]), h([]));
}
expect: {
function f(a) {
var a;
return typeof a;
}
function g() {
return"number";
}
function h(a, b) {
var a = b;
return typeof a;
}
console.log(f([]), g([]), h([]));
}
expect_stdout: "object number undefined"
}
redefine_farg_2: {
options = {
evaluate: true,
keep_fargs: false,
reduce_vars: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
function f(a) {
var a;
return typeof a;
}
function g(a) {
var a = 42;
return typeof a;
}
function h(a, b) {
var a = b;
return typeof a;
}
console.log(f([]), g([]), h([]));
}
expect: {
console.log(function(a) {
var a;
return typeof a;
}([]), function() {
return "number";
}(),function(a, b) {
var a = b;
return typeof a;
}([]));
}
expect_stdout: "object number undefined"
}
redefine_farg_3: {
options = {
evaluate: true,
keep_fargs: false,
passes: 3,
reduce_vars: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
function f(a) {
var a;
return typeof a;
}
function g(a) {
var a = 42;
return typeof a;
}
function h(a, b) {
var a = b;
return typeof a;
}
console.log(f([]), g([]), h([]));
}
expect: {
console.log(function(a) {
var a;
return typeof a;
}([]), "number", function(a) {
var a = void 0;
return typeof a;
}([]));
}
expect_stdout: "object number undefined"
}
delay_def: {
options = {
evaluate: true,
reduce_vars: true,
unused: true,
}
input: {
function f() {
return a;
var a;
}
function g() {
return a;
var a = 1;
}
console.log(f(), g());
}
expect: {
function f() {
return a;
var a;
}
function g() {
return a;
var a = 1;
}
console.log(f(), g());
}
expect_stdout: true
}

View File

@@ -306,3 +306,137 @@ unsafe_undefined: {
}
}
}
issue_1685: {
options = {
cascade: true,
side_effects: true,
}
input: {
var a = 100, b = 10;
function f() {
var a = (a--, delete a && --b);
}
f();
console.log(a, b);
}
expect: {
var a = 100, b = 10;
function f() {
var a = (a--, delete a && --b);
}
f();
console.log(a, b);
}
expect_stdout: true
}
func_def_1: {
options = {
cascade: true,
side_effects: true,
}
input: {
function f() {
return f = 0, !!f;
}
console.log(f());
}
expect: {
function f() {
return !!(f = 0);
}
console.log(f());
}
expect_stdout: "false"
}
func_def_2: {
options = {
cascade: true,
side_effects: true,
}
input: {
console.log(function f() {
return f = 0, !!f;
}());
}
expect: {
console.log(function f() {
return f = 0, !!f;
}());
}
expect_stdout: "true"
}
func_def_3: {
options = {
cascade: true,
side_effects: true,
}
input: {
function f() {
function g() {}
return g = 0, !!g;
}
console.log(f());
}
expect: {
function f() {
function g() {}
return !!(g = 0);
}
console.log(f());
}
expect_stdout: "false"
}
func_def_4: {
options = {
cascade: true,
side_effects: true,
}
input: {
function f() {
function g() {
return g = 0, !!g;
}
return g();
}
console.log(f());
}
expect: {
function f() {
function g() {
return !!(g = 0);
}
return g();
}
console.log(f());
}
expect_stdout: "false"
}
func_def_5: {
options = {
cascade: true,
side_effects: true,
}
input: {
function f() {
return function g(){
return g = 0, !!g;
}();
}
console.log(f());
}
expect: {
function f() {
return function g(){
return g = 0, !!g;
}();
}
console.log(f());
}
expect_stdout: "true"
}

View File

@@ -258,3 +258,425 @@ keep_default: {
}
}
}
issue_1663: {
options = {
dead_code: true,
evaluate: true,
}
input: {
var a = 100, b = 10;
function f() {
switch (1) {
case 1:
b = a++;
return ++b;
default:
var b;
}
}
f();
console.log(a, b);
}
expect: {
var a = 100, b = 10;
function f() {
var b;
b = a++;
return ++b;
}
f();
console.log(a, b);
}
expect_stdout: true
}
drop_case: {
options = {
dead_code: true,
}
input: {
switch (foo) {
case 'bar': baz(); break;
case 'moo':
break;
}
}
expect: {
switch (foo) {
case 'bar': baz();
}
}
}
keep_case: {
options = {
dead_code: true,
}
input: {
switch (foo) {
case 'bar': baz(); break;
case moo:
break;
}
}
expect: {
switch (foo) {
case 'bar': baz(); break;
case moo:
}
}
}
issue_376: {
options = {
dead_code: true,
evaluate: true,
}
input: {
switch (true) {
case boolCondition:
console.log(1);
break;
case false:
console.log(2);
break;
}
}
expect: {
switch (true) {
case boolCondition:
console.log(1);
}
}
}
issue_441_1: {
options = {
dead_code: true,
}
input: {
switch (foo) {
case bar:
qux();
break;
case baz:
qux();
break;
default:
qux();
break;
}
}
expect: {
switch (foo) {
case bar:
case baz:
default:
qux();
}
}
}
issue_441_2: {
options = {
dead_code: true,
}
input: {
switch (foo) {
case bar:
// TODO: Fold into the case below
qux();
break;
case fall:
case baz:
qux();
break;
default:
qux();
break;
}
}
expect: {
switch (foo) {
case bar:
qux();
break;
case fall:
case baz:
default:
qux();
}
}
}
issue_1674: {
options = {
dead_code: true,
evaluate: true,
}
input: {
switch (0) {
default:
console.log("FAIL");
break;
case 0:
console.log("PASS");
break;
}
}
expect: {
console.log("PASS");
}
expect_stdout: "PASS"
}
issue_1679: {
options = {
dead_code: true,
evaluate: true,
}
input: {
var a = 100, b = 10;
function f() {
switch (--b) {
default:
case !function x() {}:
break;
case b--:
switch (0) {
default:
case a--:
}
break;
case (a++):
break;
}
}
f();
console.log(a, b);
}
expect: {
var a = 100, b = 10;
function f() {
switch (--b) {
default:
case !function x() {}:
break;
case b--:
switch (0) {
default:
case a--:
}
break;
case (a++):
}
}
f();
console.log(a, b);
}
expect_stdout: true
}
issue_1680_1: {
options = {
dead_code: true,
evaluate: true,
}
input: {
function f(x) {
console.log(x);
return x + 1;
}
switch (2) {
case f(0):
case f(1):
f(2);
case 2:
case f(3):
case f(4):
f(5);
}
}
expect: {
function f(x) {
console.log(x);
return x + 1;
}
switch (2) {
case f(0):
case f(1):
f(2);
case 2:
f(5);
}
}
expect_stdout: [
"0",
"1",
"2",
"5",
]
}
issue_1680_2: {
options = {
dead_code: true,
}
input: {
var a = 100, b = 10;
switch (b) {
case a--:
break;
case b:
var c;
break;
case a:
break;
case a--:
break;
}
console.log(a, b);
}
expect: {
var a = 100, b = 10;
switch (b) {
case a--:
break;
case b:
var c;
break;
case a:
case a--:
}
console.log(a, b);
}
expect_stdout: true
}
issue_1690_1: {
options = {
dead_code: true,
}
input: {
switch (console.log("PASS")) {}
}
expect: {
console.log("PASS");
}
expect_stdout: "PASS"
}
issue_1690_2: {
options = {
dead_code: false,
}
input: {
switch (console.log("PASS")) {}
}
expect: {
switch (console.log("PASS")) {}
}
expect_stdout: "PASS"
}
if_switch_typeof: {
options = {
conditionals: true,
dead_code: true,
side_effects: true,
}
input: {
if (a) switch(typeof b) {}
}
expect: {
a;
}
}
issue_1698: {
options = {
side_effects: true,
}
input: {
var a = 1;
!function() {
switch (a++) {}
}();
console.log(a);
}
expect: {
var a = 1;
!function() {
switch (a++) {}
}();
console.log(a);
}
expect_stdout: "2"
}
issue_1705_1: {
options = {
dead_code: true,
}
input: {
var a = 0;
switch (a) {
default:
console.log("FAIL");
case 0:
break;
}
}
expect: {
var a = 0;
switch (a) {
default:
console.log("FAIL");
case 0:
}
}
expect_stdout: true
}
issue_1705_2: {
options = {
dead_code: true,
evaluate: true,
reduce_vars: true,
sequences: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
var a = 0;
switch (a) {
default:
console.log("FAIL");
case 0:
break;
}
}
expect: {
}
expect_stdout: true
}
issue_1705_3: {
options = {
dead_code: true,
}
input: {
switch (a) {
case 0:
break;
default:
break;
}
}
expect: {
a;
}
expect_stdout: true
}

View File

@@ -30,7 +30,6 @@ booleans_global_defs: {
expect: {
console.log(!0);
}
expect_stdout: true
}
condition_evaluate: {

View File

@@ -48,3 +48,15 @@ typeof_in_boolean_context: {
foo();
}
}
issue_1668: {
options = {
booleans: true,
}
input: {
if (typeof bar);
}
expect: {
if (!0);
}
}

View File

@@ -0,0 +1 @@
console.log(1 || 5--);

View File

@@ -0,0 +1 @@
console.log(2 || (Math.random() /= 2));

View File

@@ -0,0 +1 @@
console.log(3 || ++this);

View File

@@ -251,4 +251,59 @@ describe("bin/uglifyjs", function () {
done();
});
});
it("Should support hyphen as shorthand", function(done) {
var command = uglifyjscmd + ' test/input/issue-1431/sample.js -m keep-fnames=true';
exec(command, function (err, stdout) {
if (err) throw err;
assert.strictEqual(stdout, "function f(r){return function(){function n(n){return n*n}return r(n)}}function g(n){return n(1)+n(2)}console.log(f(g)()==5);\n");
done();
});
});
it("Should throw syntax error (5--)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/assign_1.js';
exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/assign_1.js:1,18",
"console.log(1 || 5--);",
" ^",
"SyntaxError: Invalid use of -- operator"
].join("\n"));
done();
});
});
it("Should throw syntax error (Math.random() /= 2)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/assign_2.js';
exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/assign_2.js:1,32",
"console.log(2 || (Math.random() /= 2));",
" ^",
"SyntaxError: Invalid assignment"
].join("\n"));
done();
});
});
it("Should throw syntax error (++this)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/assign_3.js';
exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/assign_3.js:1,18",
"console.log(3 || ++this);",
" ^",
"SyntaxError: Invalid use of ++ operator"
].join("\n"));
done();
});
});
});

View File

@@ -5,7 +5,7 @@ var path = require("path");
describe("minify() with input file globs", function() {
it("minify() with one input file glob string.", function() {
var result = Uglify.minify("test/input/issue-1242/foo.*");
assert.strictEqual(result.code, 'function foo(o){var n=2*o;print("Foo:",n)}var print=console.log.bind(console);');
assert.strictEqual(result.code, 'function foo(o){print("Foo:",2*o)}var print=console.log.bind(console);');
});
it("minify() with an array of one input file glob.", function() {
var result = Uglify.minify([
@@ -20,7 +20,7 @@ describe("minify() with input file globs", function() {
], {
compress: { toplevel: true }
});
assert.strictEqual(result.code, 'var print=console.log.bind(console),a=function(n){return 3*n}(3),b=function(n){return n/2}(12);print("qux",a,b),function(n){var o=2*n;print("Foo:",o)}(11);');
assert.strictEqual(result.code, 'var print=console.log.bind(console),a=function(n){return 3*n}(3),b=function(n){return n/2}(12);print("qux",a,b),function(n){print("Foo:",2*n)}(11);');
});
it("should throw with non-matching glob string", function() {
var glob = "test/input/issue-1242/blah.*";

View File

@@ -105,6 +105,23 @@ function run_compress_tests() {
function test_case(test) {
log_test(test.name);
U.base54.reset();
var output_options = test.beautify || {};
var expect;
if (test.expect) {
expect = make_code(as_toplevel(test.expect, test.mangle), output_options);
} else {
expect = test.expect_exact;
}
var input = as_toplevel(test.input, test.mangle);
var input_code = make_code(input, output_options);
var input_formatted = make_code(test.input, {
beautify: true,
quote_style: 3,
keep_quoted_props: true
});
if (test.mangle_props) {
input = U.mangle_properties(input, test.mangle_props);
}
var options = U.defaults(test.options, {
warnings: false
});
@@ -117,22 +134,6 @@ function run_compress_tests() {
if (!options.warnings) options.warnings = true;
}
var cmp = new U.Compressor(options, true);
var output_options = test.beautify || {};
var expect;
if (test.expect) {
expect = make_code(as_toplevel(test.expect, test.mangle), output_options);
} else {
expect = test.expect_exact;
}
var input = as_toplevel(test.input, test.mangle);
var input_code = make_code(test.input, {
beautify: true,
quote_style: 3,
keep_quoted_props: true
});
if (test.mangle_props) {
input = U.mangle_properties(input, test.mangle_props);
}
var output = cmp.compress(input);
output.figure_out_scope(test.mangle);
if (test.mangle) {
@@ -142,7 +143,7 @@ function run_compress_tests() {
output = make_code(output, output_options);
if (expect != output) {
log("!!! failed\n---INPUT---\n{input}\n---OUTPUT---\n{output}\n---EXPECTED---\n{expected}\n\n", {
input: input_code,
input: input_formatted,
output: output,
expected: expect
});
@@ -155,7 +156,7 @@ function run_compress_tests() {
var reparsed_ast = U.parse(output);
} catch (ex) {
log("!!! Test matched expected result but cannot parse output\n---INPUT---\n{input}\n---OUTPUT---\n{output}\n--REPARSE ERROR--\n{error}\n\n", {
input: input_code,
input: input_formatted,
output: output,
error: ex.toString(),
});
@@ -174,7 +175,7 @@ function run_compress_tests() {
var actual_warnings = JSON.stringify(warnings_emitted);
if (expected_warnings != actual_warnings) {
log("!!! failed\n---INPUT---\n{input}\n---EXPECTED WARNINGS---\n{expected_warnings}\n---ACTUAL WARNINGS---\n{actual_warnings}\n\n", {
input: input_code,
input: input_formatted,
expected_warnings: expected_warnings,
actual_warnings: actual_warnings,
});
@@ -183,13 +184,13 @@ function run_compress_tests() {
}
}
if (test.expect_stdout) {
var stdout = run_code(make_code(input, output_options));
var stdout = run_code(input_code);
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,
input: input_formatted,
expected_type: typeof test.expect_stdout == "string" ? "STDOUT" : "ERROR",
expected: test.expect_stdout,
actual_type: typeof stdout == "string" ? "STDOUT" : "ERROR",
@@ -201,7 +202,7 @@ function run_compress_tests() {
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,
input: input_formatted,
expected_type: typeof test.expect_stdout == "string" ? "STDOUT" : "ERROR",
expected: test.expect_stdout,
actual_type: typeof stdout == "string" ? "STDOUT" : "ERROR",

477
test/ufuzz.js Normal file
View File

@@ -0,0 +1,477 @@
// ufuzz.js
// derived from https://github.com/qfox/uglyfuzzer by Peter van der Zee
"use strict";
// workaround for tty output truncation upon process.exit()
[process.stdout, process.stderr].forEach(function(stream){
if (stream._handle && stream._handle.setBlocking)
stream._handle.setBlocking(true);
});
var vm = require("vm");
var minify = require("..").minify;
var MAX_GENERATED_FUNCTIONS_PER_RUN = 1;
var MAX_GENERATION_RECURSION_DEPTH = 15;
var INTERVAL_COUNT = 100;
var VALUES = [
'true',
'false',
'22',
'0',
'-0', // 0/-0 !== 0
'23..toString()',
'24 .toString()',
'25. ',
'0x26.toString()',
'(-1)',
'NaN',
'undefined',
'Infinity',
'null',
'[]',
'[,0][1]', // an array with elisions... but this is always false
'([,0].length === 2)', // an array with elisions... this is always true
'({})', // wrapped the object causes too many syntax errors in statements
'"foo"',
'"bar"' ];
var BINARY_OPS_NO_COMMA = [
' + ', // spaces needed to disambiguate with ++ cases (could otherwise cause syntax errors)
' - ',
'/',
'*',
'&',
'|',
'^',
'<<',
'>>',
'>>>',
'%',
'&&',
'||',
'^' ];
var BINARY_OPS = [','].concat(BINARY_OPS_NO_COMMA);
var ASSIGNMENTS = [
'=',
'=',
'=',
'=',
'=',
'=',
'==',
'!=',
'===',
'!==',
'+=',
'-=',
'*=',
'/=',
'&=',
'|=',
'^=',
'<<=',
'>>=',
'>>>=',
'%=' ];
var UNARY_OPS = [
'--',
'++',
'~',
'!',
'void ',
'delete ', // should be safe, even `delete foo` and `delete f()` shouldn't crash
' - ',
' + ' ];
var NO_COMMA = true;
var MAYBE = true;
var NESTED = true;
var CAN_THROW = true;
var CANNOT_THROW = false;
var CAN_BREAK = true;
var CAN_CONTINUE = true;
var VAR_NAMES = [
'foo',
'bar',
'a',
'b',
'undefined', // fun!
'eval', // mmmm, ok, also fun!
'NaN', // mmmm, ok, also fun!
'Infinity', // the fun never ends!
'arguments', // this one is just creepy
'Math', // since Math is assumed to be a non-constructor/function it may trip certain cases
'let' ]; // maybe omit this, it's more a parser problem than minifier
var TYPEOF_OUTCOMES = [
'undefined',
'string',
'number',
'object',
'boolean',
'special',
'unknown',
'symbol',
'crap' ];
var FUNC_TOSTRING = [
"Function.prototype.toString = function() {",
" var ids = [];",
" return function() {",
" var i = ids.indexOf(this);",
" if (i < 0) {",
" i = ids.length;",
" ids.push(this);",
" }",
' return "[Function: __func_" + i + "__]";',
" }",
"}();",
""
].join("\n");
function run_code(code) {
var stdout = "";
var original_write = process.stdout.write;
process.stdout.write = function(chunk) {
stdout += chunk;
};
try {
new vm.Script(FUNC_TOSTRING + code).runInNewContext({
console: {
log: function() {
return console.log.apply(console, [].map.call(arguments, function(arg) {
return typeof arg == "function" ? "[Function]" : arg;
}));
}
}
}, { timeout: 5000 });
return stdout;
} catch (ex) {
return ex;
} finally {
process.stdout.write = original_write;
}
}
function rng(max) {
return Math.floor(max * Math.random());
}
function createFunctionDecls(n, recurmax, nested) {
if (--recurmax < 0) { return ';'; }
var s = '';
while (n-- > 0) {
s += createFunctionDecl(recurmax, nested) + '\n';
}
return s;
}
var funcs = 0;
function createFunctionDecl(recurmax, nested) {
if (--recurmax < 0) { return ';'; }
var func = funcs++;
var name = rng(5) > 0 ? 'f' + func : createVarName();
if (name === 'a' || name === 'b') name = 'f' + func; // quick hack to prevent assignment to func names of being called
if (!nested && name === 'undefined' || name === 'NaN' || name === 'Infinity') name = 'f' + func; // cant redefine these in global space
var s = '';
if (rng(5) === 1) {
// functions with functions. lower the recursion to prevent a mess.
s = 'function ' + name + '(){' + createFunctionDecls(rng(5) + 1, Math.ceil(recurmax / 2), NESTED) + '}\n';
} else {
// functions with statements
s = 'function ' + name + '(){' + createStatements(3, recurmax) + '}\n';
}
if (nested) s = '!' + nested; // avoid "function statements" (decl inside statements)
else s += name + '();'
return s;
}
function createStatements(n, recurmax, canThrow, canBreak, canContinue) {
if (--recurmax < 0) { return ';'; }
var s = '';
while (--n > 0) {
s += createStatement(recurmax, canThrow, canBreak, canContinue);
}
return s;
}
var loops = 0;
function createStatement(recurmax, canThrow, canBreak, canContinue) {
var loop = ++loops;
if (--recurmax < 0) { return ';'; }
switch (rng(16)) {
case 0:
return '{' + createStatements(rng(5) + 1, recurmax, canThrow, canBreak, canContinue) + '}';
case 1:
return 'if (' + createExpression(recurmax) + ')' + createStatement(recurmax, canThrow, canBreak, canContinue) + (rng(2) === 1 ? ' else ' + createStatement(recurmax, canThrow, canBreak, canContinue) : '');
case 2:
return '{var brake' + loop + ' = 5; do {' + createStatement(recurmax, canThrow, CAN_BREAK, CAN_CONTINUE) + '} while ((' + createExpression(recurmax) + ') && --brake' + loop + ' > 0);}';
case 3:
return '{var brake' + loop + ' = 5; while ((' + createExpression(recurmax) + ') && --brake' + loop + ' > 0)' + createStatement(recurmax, canThrow, CAN_BREAK, CAN_CONTINUE) + '}';
case 4:
return 'for (var brake' + loop + ' = 5; (' + createExpression(recurmax) + ') && brake' + loop + ' > 0; --brake' + loop + ')' + createStatement(recurmax, canThrow, CAN_BREAK, CAN_CONTINUE);
case 5:
return ';';
case 6:
return createExpression(recurmax) + ';';
case 7:
// note: case args are actual expressions
// note: default does not _need_ to be last
return 'switch (' + createExpression(recurmax) + ') { ' + createSwitchParts(recurmax, 4) + '}';
case 8:
return 'var ' + createVarName() + ';';
case 9:
// initializer can only have one expression
return 'var ' + createVarName() + ' = ' + createExpression(recurmax, NO_COMMA) + ';';
case 10:
// initializer can only have one expression
return 'var ' + createVarName() + ' = ' + createExpression(recurmax, NO_COMMA) + ', ' + createVarName() + ' = ' + createExpression(recurmax, NO_COMMA) + ';';
case 11:
if (canBreak && rng(5) === 0) return 'break;';
if (canContinue && rng(5) === 0) return 'continue;';
return 'return;';
case 12:
// must wrap in curlies to prevent orphaned `else` statement
if (canThrow && rng(5) === 0) return '{ throw ' + createExpression(recurmax) + '}';
return '{ return ' + createExpression(recurmax) + '}';
case 13:
// this is actually more like a parser test, but perhaps it hits some dead code elimination traps
// must wrap in curlies to prevent orphaned `else` statement
if (canThrow && rng(5) === 0) return '{ throw\n' + createExpression(recurmax) + '}';
return '{ return\n' + createExpression(recurmax) + '}';
case 14:
// "In non-strict mode code, functions can only be declared at top level, inside a block, or ..."
// (dont both with func decls in `if`; it's only a parser thing because you cant call them without a block)
return '{' + createFunctionDecl(recurmax, NESTED) + '}';
case 15:
return ';';
// catch var could cause some problems
// note: the "blocks" are syntactically mandatory for try/catch/finally
var s = 'try {' + createStatement(recurmax, CAN_THROW, canBreak, canContinue) + ' }';
var n = rng(3); // 0=only catch, 1=only finally, 2=catch+finally
if (n !== 1) s += ' catch (' + createVarName() + ') { ' + createStatements(3, recurmax, canBreak, canContinue) + ' }';
if (n !== 0) s += ' finally { ' + createStatements(3, recurmax, canBreak, canContinue) + ' }';
return s;
}
}
function createSwitchParts(recurmax, n) {
var hadDefault = false;
var s = '';
while (n-- > 0) {
hadDefault = n > 0;
if (hadDefault || rng(4) > 0) {
s += '' +
'case ' + createExpression(recurmax) + ':\n' +
createStatements(rng(3) + 1, recurmax, CANNOT_THROW, CAN_BREAK) +
'\n' +
(rng(10) > 0 ? ' break;' : '/* fall-through */') +
'\n';
} else {
hadDefault = true;
s += '' +
'default:\n' +
createStatements(rng(3) + 1, recurmax, CANNOT_THROW, CAN_BREAK) +
'\n';
}
}
return s;
}
function createExpression(recurmax, noComma) {
if (--recurmax < 0) {
return createValue(); // note: should return a simple non-recursing expression value!
}
switch (rng(12)) {
case 0:
return '(' + createUnaryOp() + (rng(2) === 1 ? 'a' : 'b') + ')';
case 1:
return '(a' + (rng(2) == 1 ? '++' : '--') + ')';
case 2:
return '(b ' + createAssignment() + ' a)';
case 3:
return '(' + rng(2) + ' === 1 ? a : b)';
case 4:
return createExpression(recurmax, noComma) + createBinaryOp(noComma) + createExpression(recurmax, noComma);
case 5:
return createValue();
case 6:
return '(' + createExpression(recurmax) + ')';
case 7:
return createExpression(recurmax, noComma) + '?(' + createExpression(recurmax) + '):(' + createExpression(recurmax) + ')';
case 8:
switch(rng(4)) {
case 0:
return '(function ' + createVarName(MAYBE) + '(){' + createStatements(rng(5) + 1, recurmax) + '})()';
case 1:
return '+function ' + createVarName(MAYBE) + '(){' + createStatements(rng(5) + 1, recurmax) + '}';
case 2:
return '!function ' + createVarName(MAYBE) + '(){' + createStatements(rng(5) + 1, recurmax) + '}';
case 3:
return 'void function ' + createVarName(MAYBE) + '(){' + createStatements(rng(5) + 1, recurmax) + '}';
default:
return 'void function ' + createVarName(MAYBE) + '(){' + createStatements(rng(5) + 1, recurmax) + '}';
}
case 9:
return createTypeofExpr(recurmax);
case 10:
// you could statically infer that this is just `Math`, regardless of the other expression
// I don't think Uglify does this at this time...
return ''+
'new function(){ \n' +
(rng(2) === 1 ? createExpression(recurmax) + '\n' : '') +
'return Math;\n' +
'}';
case 11:
// more like a parser test but perhaps comment nodes mess up the analysis?
switch (rng(5)) {
case 0:
return '(a/* ignore */++)';
case 1:
return '(b/* ignore */--)';
case 2:
return '(++/* ignore */a)';
case 3:
return '(--/* ignore */b)';
case 4:
// only groups that wrap a single variable return a "Reference", so this is still valid.
// may just be a parser edge case that is invisible to uglify...
return '(--(b))';
default:
return '(--/* ignore */b)';
}
}
}
function createTypeofExpr(recurmax) {
if (--recurmax < 0) {
return 'typeof undefined === "undefined"';
}
switch (rng(5)) {
case 0:
return '(typeof ' + createVarName() + ' === "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '")';
case 1:
return '(typeof ' + createVarName() + ' !== "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '")';
case 2:
return '(typeof ' + createVarName() + ' == "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '")';
case 3:
return '(typeof ' + createVarName() + ' != "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '")';
case 4:
return '(typeof ' + createVarName() + ')';
}
}
function createValue() {
return VALUES[rng(VALUES.length)];
}
function createBinaryOp(noComma) {
if (noComma) return BINARY_OPS_NO_COMMA[rng(BINARY_OPS_NO_COMMA.length)];
return BINARY_OPS[rng(BINARY_OPS.length)];
}
function createAssignment() {
return ASSIGNMENTS[rng(ASSIGNMENTS.length)];
}
function createUnaryOp() {
return UNARY_OPS[rng(UNARY_OPS.length)];
}
function createVarName(maybe) {
if (!maybe || rng(2) === 1) {
return VAR_NAMES[rng(VAR_NAMES.length)] + (rng(5) > 0 ? ++loops : '');
}
return '';
}
function log(ok) {
console.log("//=============================================================");
if (!ok) console.log("// !!!!!! Failed...");
console.log("// original code");
console.log("//");
console.log(original_code);
console.log();
console.log();
console.log("//-------------------------------------------------------------");
console.log("// original code (beautify'd)");
console.log("//");
console.log(beautify_code);
console.log();
console.log();
console.log("//-------------------------------------------------------------");
console.log("// uglified code");
console.log("//");
console.log(uglify_code);
console.log();
console.log();
console.log("original result:");
console.log(original_result);
console.log("beautified result:");
console.log(beautify_result);
console.log("uglified result:");
console.log(uglify_result);
if (!ok) console.log("!!!!!! Failed...");
}
var num_iterations = +process.argv[2] || 1/0;
var verbose = process.argv[3] === 'v' || process.argv[2] === 'v';
var verbose_interval = process.argv[3] === 'V' || process.argv[2] === 'V';
for (var round = 0; round < num_iterations; round++) {
var parse_error = false;
process.stdout.write(round + " of " + num_iterations + "\r");
var original_code = [
"var a = 100, b = 10;",
createFunctionDecls(rng(MAX_GENERATED_FUNCTIONS_PER_RUN) + 1, MAX_GENERATION_RECURSION_DEPTH),
"console.log(a, b);"
].join("\n");
var original_result = run_code(original_code);
try {
var beautify_code = minify(original_code, {
fromString: true,
mangle: false,
compress: false,
output: {
beautify: true,
bracketize: true,
},
}).code;
} catch(e) {
parse_error = 1;
}
var beautify_result = run_code(beautify_code);
try {
var uglify_code = minify(beautify_code, {
fromString: true,
mangle: true,
compress: {
passes: 3,
},
output: {
//beautify: true,
//bracketize: true,
},
}).code;
} catch(e) {
parse_error = 2;
}
var uglify_result = run_code(uglify_code);
var ok = !parse_error && original_result == beautify_result && original_result == uglify_result;
if (verbose || (verbose_interval && !(round % INTERVAL_COUNT)) || !ok) log(ok);
if (parse_error === 1) console.log('Parse error while beautifying');
if (parse_error === 2) console.log('Parse error while uglifying');
if (!ok) break;
}