Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d13b71297e | ||
|
|
457f958af3 | ||
|
|
53517db3e4 | ||
|
|
c13caf4876 | ||
|
|
fbfa6178a6 | ||
|
|
5315dd95b0 | ||
|
|
31a7bf2a22 | ||
|
|
f0a29902ac | ||
|
|
0d820e4c0a | ||
|
|
f01f580d6c | ||
|
|
c01ff76288 | ||
|
|
83a42716c3 | ||
|
|
2557148bba | ||
|
|
dd22eda888 | ||
|
|
f4c77886e7 | ||
|
|
df547ffd97 | ||
|
|
70551febc8 | ||
|
|
44499a6643 | ||
|
|
470a7d4df1 | ||
|
|
551420132c | ||
|
|
b0040ba654 | ||
|
|
c93ca6ee53 | ||
|
|
df506439b1 |
10
README.md
10
README.md
@@ -848,8 +848,14 @@ can pass additional arguments that control the code output:
|
||||
statement.
|
||||
|
||||
- `comments` (default `false`) -- pass `true` or `"all"` to preserve all
|
||||
comments, `"some"` to preserve some comments, a regular expression string
|
||||
(e.g. `/^!/`) or a function.
|
||||
comments, `"some"` to preserve multi-line comments that contain `@cc_on`,
|
||||
`@license`, or `@preserve` (case-insensitive), a regular expression string
|
||||
(e.g. `/^!/`), or a function which returns `boolean`, e.g.
|
||||
```js
|
||||
function(node, comment) {
|
||||
return comment.value.indexOf("@type " + node.TYPE) >= 0;
|
||||
}
|
||||
```
|
||||
|
||||
- `indent_level` (default `4`)
|
||||
|
||||
|
||||
11
bin/uglifyjs
11
bin/uglifyjs
@@ -54,6 +54,7 @@ program.option("--toplevel", "Compress and/or mangle variables in toplevel scope
|
||||
program.option("--verbose", "Print diagnostic messages.");
|
||||
program.option("--warn", "Print warning messages.");
|
||||
program.option("--wrap <name>", "Embed everything as a function with “exports” corresponding to “name” globally.");
|
||||
program.option("--reduce-test", "Reduce a standalone `console.log` based test case.");
|
||||
program.arguments("[files...]").parseArgv(process.argv);
|
||||
if (program.configFile) {
|
||||
options = JSON.parse(read_file(program.configFile));
|
||||
@@ -215,7 +216,15 @@ function run() {
|
||||
} catch (ex) {
|
||||
fatal(ex);
|
||||
}
|
||||
var result = UglifyJS.minify(files, options);
|
||||
if (program.reduceTest) {
|
||||
// load on demand - assumes dev tree checked out
|
||||
var reduce_test = require("../test/reduce");
|
||||
var testcase = files[0] || files[Object.keys(files)[0]];
|
||||
var result = reduce_test(testcase, options, {verbose: true});
|
||||
}
|
||||
else {
|
||||
var result = UglifyJS.minify(files, options);
|
||||
}
|
||||
if (result.error) {
|
||||
var ex = result.error;
|
||||
if (ex.name == "SyntaxError") {
|
||||
|
||||
@@ -169,10 +169,7 @@ var AST_SimpleStatement = DEFNODE("SimpleStatement", "body", {
|
||||
}, AST_Statement);
|
||||
|
||||
function walk_body(node, visitor) {
|
||||
var body = node.body;
|
||||
if (body instanceof AST_Statement) {
|
||||
body._walk(visitor);
|
||||
} else body.forEach(function(node) {
|
||||
node.body.forEach(function(node) {
|
||||
node._walk(visitor);
|
||||
});
|
||||
}
|
||||
@@ -351,7 +348,7 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", {
|
||||
filename: "wrap=" + JSON.stringify(name)
|
||||
}).transform(new TreeTransformer(function(node) {
|
||||
if (node instanceof AST_Directive && node.value == "$ORIG") {
|
||||
return MAP.splice(body);
|
||||
return List.splice(body);
|
||||
}
|
||||
}));
|
||||
},
|
||||
@@ -370,7 +367,7 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", {
|
||||
filename: "enclose=" + JSON.stringify(args_values)
|
||||
}).transform(new TreeTransformer(function(node) {
|
||||
if (node instanceof AST_Directive && node.value == "$ORIG") {
|
||||
return MAP.splice(body);
|
||||
return List.splice(body);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
358
lib/compress.js
358
lib/compress.js
@@ -1230,7 +1230,7 @@ merge(Compressor.prototype, {
|
||||
var parent = multi_replacer.parent();
|
||||
if (parent instanceof AST_Sequence && parent.tail_node() !== node) {
|
||||
value_def.replaced++;
|
||||
return MAP.skip;
|
||||
return List.skip;
|
||||
}
|
||||
return get_rvalue(candidate);
|
||||
case 1:
|
||||
@@ -1863,7 +1863,7 @@ merge(Compressor.prototype, {
|
||||
node.value = null;
|
||||
return node;
|
||||
}
|
||||
return in_list ? MAP.skip : null;
|
||||
return in_list ? List.skip : null;
|
||||
}, patch_sequence));
|
||||
}
|
||||
|
||||
@@ -3071,12 +3071,17 @@ merge(Compressor.prototype, {
|
||||
// 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) {
|
||||
//
|
||||
// When `ignore_side_effects` is `true`, inspect the constant value
|
||||
// produced without worrying about any side effects caused by said
|
||||
// expression.
|
||||
AST_Node.DEFMETHOD("evaluate", function(compressor, ignore_side_effects) {
|
||||
if (!compressor.option("evaluate")) return this;
|
||||
var cached = [];
|
||||
var val = this._eval(compressor, cached, 1);
|
||||
var val = this._eval(compressor, ignore_side_effects, cached, 1);
|
||||
cached.forEach(function(node) {
|
||||
delete node._eval;
|
||||
});
|
||||
@@ -3104,6 +3109,19 @@ merge(Compressor.prototype, {
|
||||
def(AST_Constant, function() {
|
||||
return this.value;
|
||||
});
|
||||
def(AST_Assign, function(compressor, ignore_side_effects, cached, depth) {
|
||||
if (!ignore_side_effects) return this;
|
||||
if (this.operator != "=") return this;
|
||||
var node = this.right;
|
||||
var value = node._eval(compressor, ignore_side_effects, cached, depth);
|
||||
return value === node ? this : value;
|
||||
});
|
||||
def(AST_Sequence, function(compressor, ignore_side_effects, cached, depth) {
|
||||
if (!ignore_side_effects) return this;
|
||||
var node = this.tail_node();
|
||||
var value = node._eval(compressor, ignore_side_effects, cached, depth);
|
||||
return value === node ? this : value;
|
||||
});
|
||||
def(AST_Function, function(compressor) {
|
||||
if (compressor.option("unsafe")) {
|
||||
var fn = function() {};
|
||||
@@ -3115,12 +3133,12 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
return this;
|
||||
});
|
||||
def(AST_Array, function(compressor, cached, depth) {
|
||||
def(AST_Array, function(compressor, ignore_side_effects, cached, depth) {
|
||||
if (compressor.option("unsafe")) {
|
||||
var elements = [];
|
||||
for (var i = 0; i < this.elements.length; i++) {
|
||||
var element = this.elements[i];
|
||||
var value = element._eval(compressor, cached, depth);
|
||||
var value = element._eval(compressor, ignore_side_effects, cached, depth);
|
||||
if (element === value) return this;
|
||||
elements.push(value);
|
||||
}
|
||||
@@ -3128,7 +3146,7 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
return this;
|
||||
});
|
||||
def(AST_Object, function(compressor, cached, depth) {
|
||||
def(AST_Object, function(compressor, ignore_side_effects, cached, depth) {
|
||||
if (compressor.option("unsafe")) {
|
||||
var val = {};
|
||||
for (var i = 0; i < this.properties.length; i++) {
|
||||
@@ -3137,14 +3155,14 @@ merge(Compressor.prototype, {
|
||||
if (key instanceof AST_Symbol) {
|
||||
key = key.name;
|
||||
} else if (key instanceof AST_Node) {
|
||||
key = key._eval(compressor, cached, depth);
|
||||
key = key._eval(compressor, ignore_side_effects, cached, depth);
|
||||
if (key === prop.key) return this;
|
||||
}
|
||||
if (typeof Object.prototype[key] === 'function') {
|
||||
return this;
|
||||
}
|
||||
if (prop.value instanceof AST_Function) continue;
|
||||
val[key] = prop.value._eval(compressor, cached, depth);
|
||||
val[key] = prop.value._eval(compressor, ignore_side_effects, cached, depth);
|
||||
if (val[key] === prop.value) return this;
|
||||
}
|
||||
return val;
|
||||
@@ -3152,7 +3170,7 @@ merge(Compressor.prototype, {
|
||||
return this;
|
||||
});
|
||||
var non_converting_unary = makePredicate("! typeof void");
|
||||
def(AST_UnaryPrefix, function(compressor, cached, depth) {
|
||||
def(AST_UnaryPrefix, function(compressor, ignore_side_effects, cached, depth) {
|
||||
var e = this.expression;
|
||||
// Function would be evaluated to an array and so typeof would
|
||||
// incorrectly return 'object'. Hence making is a special case.
|
||||
@@ -3164,7 +3182,7 @@ merge(Compressor.prototype, {
|
||||
return typeof function(){};
|
||||
}
|
||||
if (!non_converting_unary[this.operator]) depth++;
|
||||
var v = e._eval(compressor, cached, depth);
|
||||
var v = e._eval(compressor, ignore_side_effects, cached, depth);
|
||||
if (v === this.expression) return this;
|
||||
switch (this.operator) {
|
||||
case "!": return !v;
|
||||
@@ -3187,12 +3205,12 @@ merge(Compressor.prototype, {
|
||||
return this;
|
||||
});
|
||||
var non_converting_binary = makePredicate("&& || === !==");
|
||||
def(AST_Binary, function(compressor, cached, depth) {
|
||||
def(AST_Binary, function(compressor, ignore_side_effects, cached, depth) {
|
||||
if (!non_converting_binary[this.operator]) depth++;
|
||||
var left = this.left._eval(compressor, cached, depth);
|
||||
var left = this.left._eval(compressor, ignore_side_effects, cached, depth);
|
||||
if (left === this.left) return this;
|
||||
if (this.operator == (left ? "||" : "&&")) return left;
|
||||
var right = this.right._eval(compressor, cached, depth);
|
||||
var right = this.right._eval(compressor, ignore_side_effects, cached, depth);
|
||||
if (right === this.right) return this;
|
||||
var result;
|
||||
switch (this.operator) {
|
||||
@@ -3235,14 +3253,14 @@ merge(Compressor.prototype, {
|
||||
return (match[1] || ".").length - 1 - (match[2] || "").slice(1);
|
||||
}
|
||||
});
|
||||
def(AST_Conditional, function(compressor, cached, depth) {
|
||||
var condition = this.condition._eval(compressor, cached, depth);
|
||||
def(AST_Conditional, function(compressor, ignore_side_effects, cached, depth) {
|
||||
var condition = this.condition._eval(compressor, ignore_side_effects, cached, depth);
|
||||
if (condition === this.condition) return this;
|
||||
var node = condition ? this.consequent : this.alternative;
|
||||
var value = node._eval(compressor, cached, depth);
|
||||
var value = node._eval(compressor, ignore_side_effects, cached, depth);
|
||||
return value === node ? this : value;
|
||||
});
|
||||
def(AST_SymbolRef, function(compressor, cached, depth) {
|
||||
def(AST_SymbolRef, function(compressor, ignore_side_effects, cached, depth) {
|
||||
var fixed = this.fixed_value();
|
||||
if (!fixed) return this;
|
||||
var value;
|
||||
@@ -3250,7 +3268,7 @@ merge(Compressor.prototype, {
|
||||
value = fixed._eval();
|
||||
} else {
|
||||
this._eval = return_this;
|
||||
value = fixed._eval(compressor, cached, depth);
|
||||
value = fixed._eval(compressor, ignore_side_effects, cached, depth);
|
||||
delete this._eval;
|
||||
if (value === fixed) return this;
|
||||
fixed._eval = function() {
|
||||
@@ -3307,11 +3325,11 @@ merge(Compressor.prototype, {
|
||||
],
|
||||
});
|
||||
var regexp_props = makePredicate("global ignoreCase multiline source");
|
||||
def(AST_PropAccess, function(compressor, cached, depth) {
|
||||
def(AST_PropAccess, function(compressor, ignore_side_effects, cached, depth) {
|
||||
if (compressor.option("unsafe")) {
|
||||
var key = this.property;
|
||||
if (key instanceof AST_Node) {
|
||||
key = key._eval(compressor, cached, depth);
|
||||
key = key._eval(compressor, ignore_side_effects, cached, depth);
|
||||
if (key === this.property) return this;
|
||||
}
|
||||
var exp = this.expression;
|
||||
@@ -3321,7 +3339,7 @@ merge(Compressor.prototype, {
|
||||
if (!static_value || !static_value[key]) return this;
|
||||
val = global_objs[exp.name];
|
||||
} else {
|
||||
val = exp._eval(compressor, cached, depth + 1);
|
||||
val = exp._eval(compressor, ignore_side_effects, cached, depth + 1);
|
||||
if (val == null || val === exp) return this;
|
||||
if (val instanceof RegExp) {
|
||||
if (!regexp_props[key]) return this;
|
||||
@@ -3340,7 +3358,7 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
return this;
|
||||
});
|
||||
def(AST_Call, function(compressor, cached, depth) {
|
||||
def(AST_Call, function(compressor, ignore_side_effects, cached, depth) {
|
||||
var exp = this.expression;
|
||||
var fn = exp instanceof AST_SymbolRef ? exp.fixed_value() : exp;
|
||||
if (fn instanceof AST_Lambda) {
|
||||
@@ -3361,14 +3379,14 @@ merge(Compressor.prototype, {
|
||||
});
|
||||
});
|
||||
fn.evaluating = true;
|
||||
var val = stat.value._eval(compressor, cached, depth);
|
||||
var val = stat.value._eval(compressor, ignore_side_effects, cached, depth);
|
||||
delete fn.evaluating;
|
||||
if (val === stat.value) return this;
|
||||
return val;
|
||||
} else if (compressor.option("unsafe") && exp instanceof AST_PropAccess) {
|
||||
var key = exp.property;
|
||||
if (key instanceof AST_Node) {
|
||||
key = key._eval(compressor, cached, depth);
|
||||
key = key._eval(compressor, ignore_side_effects, cached, depth);
|
||||
if (key === exp.property) return this;
|
||||
}
|
||||
var val;
|
||||
@@ -3378,7 +3396,7 @@ merge(Compressor.prototype, {
|
||||
if (!static_fn || !static_fn[key]) return this;
|
||||
val = global_objs[e.name];
|
||||
} else {
|
||||
val = e._eval(compressor, cached, depth + 1);
|
||||
val = e._eval(compressor, ignore_side_effects, cached, depth + 1);
|
||||
if (val == null || val === e) return this;
|
||||
var native_fn = native_fns[val.constructor.name];
|
||||
if (!native_fn || !native_fn[key]) return this;
|
||||
@@ -3403,7 +3421,7 @@ merge(Compressor.prototype, {
|
||||
var values = [];
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
var arg = args[i];
|
||||
var value = arg._eval(compressor, cached, depth);
|
||||
var value = arg._eval(compressor, ignore_side_effects, cached, depth);
|
||||
if (arg === value) return;
|
||||
values.push(value);
|
||||
}
|
||||
@@ -3988,6 +4006,8 @@ merge(Compressor.prototype, {
|
||||
};
|
||||
// pass 3: we should drop declarations not in_use
|
||||
var unused_fn_names = [];
|
||||
var calls_to_drop_args = [];
|
||||
var fns_with_marked_args = [];
|
||||
var tt = new TreeTransformer(function(node, descend, in_list) {
|
||||
var parent = tt.parent();
|
||||
if (drop_vars) {
|
||||
@@ -4017,7 +4037,7 @@ merge(Compressor.prototype, {
|
||||
if (value) props.push(value);
|
||||
switch (props.length) {
|
||||
case 0:
|
||||
return MAP.skip;
|
||||
return List.skip;
|
||||
case 1:
|
||||
return maintain_this_binding(compressor, parent, node, props[0].transform(tt));
|
||||
default:
|
||||
@@ -4028,6 +4048,7 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node instanceof AST_Call) calls_to_drop_args.push(node);
|
||||
if (scope !== self) return;
|
||||
if (node instanceof AST_Function && node.name && drop_fn_name(node.name.definition())) {
|
||||
unused_fn_names.push(node);
|
||||
@@ -4046,6 +4067,7 @@ merge(Compressor.prototype, {
|
||||
trim = false;
|
||||
}
|
||||
}
|
||||
fns_with_marked_args.push(node);
|
||||
}
|
||||
if (drop_funcs && node instanceof AST_Defun && node !== self) {
|
||||
var def = node.name.definition();
|
||||
@@ -4159,11 +4181,11 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
switch (body.length) {
|
||||
case 0:
|
||||
return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
|
||||
return in_list ? List.skip : make_node(AST_EmptyStatement, node);
|
||||
case 1:
|
||||
return body[0];
|
||||
default:
|
||||
return in_list ? MAP.splice(body) : make_node(AST_BlockStatement, node, {
|
||||
return in_list ? List.splice(body) : make_node(AST_BlockStatement, node, {
|
||||
body: body
|
||||
});
|
||||
}
|
||||
@@ -4177,7 +4199,7 @@ merge(Compressor.prototype, {
|
||||
var block = node.body;
|
||||
node.body = block.body.pop();
|
||||
block.body.push(node);
|
||||
return in_list ? MAP.splice(block.body) : block;
|
||||
return in_list ? List.splice(block.body) : block;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
@@ -4214,7 +4236,7 @@ merge(Compressor.prototype, {
|
||||
} else if (is_empty(node.init)) {
|
||||
node.init = null;
|
||||
}
|
||||
return !block ? node : in_list ? MAP.splice(block.body) : block;
|
||||
return !block ? node : in_list ? List.splice(block.body) : block;
|
||||
} else if (node instanceof AST_ForIn) {
|
||||
if (!drop_vars || !compressor.option("loops")) return;
|
||||
if (!(node.init instanceof AST_Definitions)) return;
|
||||
@@ -4229,7 +4251,7 @@ merge(Compressor.prototype, {
|
||||
body: value
|
||||
});
|
||||
}
|
||||
return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
|
||||
return in_list ? List.skip : make_node(AST_EmptyStatement, node);
|
||||
} else if (node instanceof AST_Sequence) {
|
||||
if (node.expressions.length == 1) return node.expressions[0];
|
||||
}
|
||||
@@ -4239,6 +4261,9 @@ merge(Compressor.prototype, {
|
||||
unused_fn_names.forEach(function(fn) {
|
||||
fn.name = null;
|
||||
});
|
||||
calls_to_drop_args.forEach(function(call) {
|
||||
drop_unused_call_args(call, compressor, fns_with_marked_args);
|
||||
});
|
||||
|
||||
function log(sym, text, props) {
|
||||
AST_Node[sym.unreferenced() ? "warn" : "info"](text, props);
|
||||
@@ -4478,7 +4503,7 @@ merge(Compressor.prototype, {
|
||||
node.in_bool = true;
|
||||
var value = node.value;
|
||||
if (value) {
|
||||
var ev = value.is_truthy() || value.tail_node().evaluate(compressor);
|
||||
var ev = value.is_truthy() || value.evaluate(compressor, true);
|
||||
if (!ev) {
|
||||
value = value.drop_side_effect_free(compressor);
|
||||
node.value = value ? make_sequence(node.value, [
|
||||
@@ -4610,7 +4635,7 @@ merge(Compressor.prototype, {
|
||||
}));
|
||||
});
|
||||
defs_by_id[node.name.definition().id] = defs;
|
||||
return MAP.splice(var_defs);
|
||||
return List.splice(var_defs);
|
||||
}
|
||||
|
||||
function make_sym(sym, key) {
|
||||
@@ -4746,21 +4771,17 @@ merge(Compressor.prototype, {
|
||||
return exprs && make_sequence(this, exprs);
|
||||
}
|
||||
if (exp instanceof AST_Function && (!exp.name || !exp.name.definition().references.length)) {
|
||||
var node = this.clone();
|
||||
exp.process_expression(false, function(node) {
|
||||
var value = node.value && node.value.drop_side_effect_free(compressor, true);
|
||||
return value ? make_node(AST_SimpleStatement, node, {
|
||||
body: value
|
||||
}) : make_node(AST_EmptyStatement, node);
|
||||
});
|
||||
exp.walk(new TreeWalker(function(node) {
|
||||
if (node instanceof AST_Return && node.value) {
|
||||
node.value = node.value.drop_side_effect_free(compressor);
|
||||
return true;
|
||||
}
|
||||
if (node instanceof AST_Scope && node !== exp) return true;
|
||||
}));
|
||||
return node;
|
||||
scan_local_returns(exp, function(node) {
|
||||
if (node.value) node.value = node.value.drop_side_effect_free(compressor);
|
||||
});
|
||||
// always shallow clone to ensure stripping of negated IIFEs
|
||||
return this.clone();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -4878,7 +4899,7 @@ merge(Compressor.prototype, {
|
||||
|
||||
OPT(AST_Do, function(self, compressor) {
|
||||
if (!compressor.option("loops")) return self;
|
||||
var cond = self.condition.is_truthy() || self.condition.tail_node().evaluate(compressor);
|
||||
var cond = self.condition.is_truthy() || self.condition.evaluate(compressor, true);
|
||||
if (!(cond instanceof AST_Node)) {
|
||||
if (cond) return make_node(AST_For, self, {
|
||||
body: make_node(AST_BlockStatement, self.body, {
|
||||
@@ -5008,16 +5029,14 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
if (self.condition) {
|
||||
var cond = self.condition.evaluate(compressor);
|
||||
if (!(cond instanceof AST_Node)) {
|
||||
if (cond) self.condition = null;
|
||||
else if (!compressor.option("dead_code")) {
|
||||
var orig = self.condition;
|
||||
self.condition = make_node_from_constant(cond, self.condition);
|
||||
self.condition = best_of_expression(self.condition.transform(compressor), orig);
|
||||
}
|
||||
}
|
||||
if (cond instanceof AST_Node) {
|
||||
cond = self.condition.is_truthy() || self.condition.tail_node().evaluate(compressor);
|
||||
cond = self.condition.is_truthy() || self.condition.evaluate(compressor, true);
|
||||
} else if (cond) {
|
||||
self.condition = null;
|
||||
} else if (!compressor.option("dead_code")) {
|
||||
var orig = self.condition;
|
||||
self.condition = make_node_from_constant(cond, self.condition);
|
||||
self.condition = best_of_expression(self.condition.transform(compressor), orig);
|
||||
}
|
||||
if (!cond) {
|
||||
if (compressor.option("dead_code")) {
|
||||
@@ -5109,7 +5128,7 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
if (compressor.option("dead_code")) {
|
||||
if (cond instanceof AST_Node) {
|
||||
cond = self.condition.is_truthy() || self.condition.tail_node().evaluate(compressor);
|
||||
cond = self.condition.is_truthy() || self.condition.evaluate(compressor, true);
|
||||
}
|
||||
if (!cond) {
|
||||
AST_Node.warn("Condition always false [{file}:{line},{col}]", self.condition.start);
|
||||
@@ -5255,7 +5274,7 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
if (!compressor.option("dead_code")) return self;
|
||||
if (value instanceof AST_Node) {
|
||||
value = self.expression.tail_node().evaluate(compressor);
|
||||
value = self.expression.evaluate(compressor, true);
|
||||
}
|
||||
var decl = [];
|
||||
var body = [];
|
||||
@@ -5277,7 +5296,7 @@ merge(Compressor.prototype, {
|
||||
eliminate_branch(branch, body[body.length - 1]);
|
||||
continue;
|
||||
}
|
||||
if (exp instanceof AST_Node) exp = branch.expression.tail_node().evaluate(compressor);
|
||||
if (exp instanceof AST_Node) exp = branch.expression.evaluate(compressor, true);
|
||||
if (exp === value) {
|
||||
exact_match = branch;
|
||||
if (default_branch) {
|
||||
@@ -5437,6 +5456,62 @@ merge(Compressor.prototype, {
|
||||
return make_sequence(node, x);
|
||||
}
|
||||
|
||||
function drop_unused_call_args(call, compressor, fns_with_marked_args) {
|
||||
var exp = call.expression;
|
||||
var fn = exp instanceof AST_SymbolRef ? exp.fixed_value() : exp;
|
||||
if (!(fn instanceof AST_Lambda)) return;
|
||||
if (fn.uses_arguments) return;
|
||||
if (fn.pinned()) return;
|
||||
if (fns_with_marked_args && fns_with_marked_args.indexOf(fn) < 0) return;
|
||||
var args = call.args;
|
||||
var pos = 0, last = 0;
|
||||
var drop_fargs = fn === exp && !fn.name && compressor.drop_fargs(fn, call);
|
||||
var side_effects = [];
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
var trim = i >= fn.argnames.length;
|
||||
if (trim || fn.argnames[i].__unused) {
|
||||
var node = args[i].drop_side_effect_free(compressor);
|
||||
if (drop_fargs) {
|
||||
fn.argnames.splice(i, 1);
|
||||
args.splice(i, 1);
|
||||
if (node) side_effects.push(node);
|
||||
i--;
|
||||
continue;
|
||||
} else if (node) {
|
||||
side_effects.push(node);
|
||||
args[pos++] = make_sequence(call, side_effects);
|
||||
side_effects = [];
|
||||
} else if (!trim) {
|
||||
if (side_effects.length) {
|
||||
node = make_sequence(call, side_effects);
|
||||
side_effects = [];
|
||||
} else {
|
||||
node = make_node(AST_Number, args[i], {
|
||||
value: 0
|
||||
});
|
||||
}
|
||||
args[pos++] = node;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
side_effects.push(args[i]);
|
||||
args[pos++] = make_sequence(call, side_effects);
|
||||
side_effects = [];
|
||||
}
|
||||
last = pos;
|
||||
}
|
||||
if (drop_fargs) for (; i < fn.argnames.length; i++) {
|
||||
if (fn.argnames[i].__unused) fn.argnames.splice(i--, 1);
|
||||
}
|
||||
args.length = last;
|
||||
if (!side_effects.length) return;
|
||||
var arg = make_sequence(call, side_effects);
|
||||
args.push(args.length < fn.argnames.length ? make_node(AST_UnaryPrefix, call, {
|
||||
operator: "void",
|
||||
expression: arg
|
||||
}) : arg);
|
||||
}
|
||||
|
||||
OPT(AST_Call, function(self, compressor) {
|
||||
var exp = self.expression;
|
||||
if (compressor.option("sequences")) {
|
||||
@@ -5453,63 +5528,7 @@ merge(Compressor.prototype, {
|
||||
if (seq !== self) return seq.optimize(compressor);
|
||||
}
|
||||
}
|
||||
var fn = exp;
|
||||
if (compressor.option("reduce_vars") && fn instanceof AST_SymbolRef) {
|
||||
fn = fn.fixed_value();
|
||||
}
|
||||
var is_func = fn instanceof AST_Lambda;
|
||||
if (compressor.option("unused")
|
||||
&& is_func
|
||||
&& !fn.uses_arguments
|
||||
&& !fn.pinned()) {
|
||||
var pos = 0, last = 0;
|
||||
var drop_fargs = exp === fn && !fn.name && compressor.drop_fargs(fn, self);
|
||||
var side_effects = [];
|
||||
for (var i = 0; i < self.args.length; i++) {
|
||||
var trim = i >= fn.argnames.length;
|
||||
if (trim || fn.argnames[i].__unused) {
|
||||
var node = self.args[i].drop_side_effect_free(compressor);
|
||||
if (drop_fargs) {
|
||||
fn.argnames.splice(i, 1);
|
||||
self.args.splice(i, 1);
|
||||
if (node) side_effects.push(node);
|
||||
i--;
|
||||
continue;
|
||||
} else if (node) {
|
||||
side_effects.push(node);
|
||||
self.args[pos++] = make_sequence(self, side_effects);
|
||||
side_effects = [];
|
||||
} else if (!trim) {
|
||||
if (side_effects.length) {
|
||||
node = make_sequence(self, side_effects);
|
||||
side_effects = [];
|
||||
} else {
|
||||
node = make_node(AST_Number, self.args[i], {
|
||||
value: 0
|
||||
});
|
||||
}
|
||||
self.args[pos++] = node;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
side_effects.push(self.args[i]);
|
||||
self.args[pos++] = make_sequence(self, side_effects);
|
||||
side_effects = [];
|
||||
}
|
||||
last = pos;
|
||||
}
|
||||
if (drop_fargs) for (; i < fn.argnames.length; i++) {
|
||||
if (fn.argnames[i].__unused) fn.argnames.splice(i--, 1);
|
||||
}
|
||||
self.args.length = last;
|
||||
if (side_effects.length) {
|
||||
var arg = make_sequence(self, side_effects);
|
||||
self.args.push(self.args.length < fn.argnames.length ? make_node(AST_UnaryPrefix, self, {
|
||||
operator: "void",
|
||||
expression: arg
|
||||
}) : arg);
|
||||
}
|
||||
}
|
||||
if (compressor.option("unused")) drop_unused_call_args(self, compressor);
|
||||
if (compressor.option("unsafe")) {
|
||||
if (is_undeclared_ref(exp)) switch (exp.name) {
|
||||
case "Array":
|
||||
@@ -5775,6 +5794,8 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
}
|
||||
}
|
||||
var fn = exp instanceof AST_SymbolRef ? exp.fixed_value() : exp;
|
||||
var is_func = fn instanceof AST_Lambda;
|
||||
var stat = is_func && fn.first_statement();
|
||||
var can_inline = compressor.option("inline") && !self.is_expr_pure(compressor);
|
||||
if (exp === fn && can_inline && stat instanceof AST_Return) {
|
||||
@@ -6162,34 +6183,43 @@ merge(Compressor.prototype, {
|
||||
});
|
||||
|
||||
AST_Binary.DEFMETHOD("lift_sequences", function(compressor) {
|
||||
if (compressor.option("sequences")) {
|
||||
if (this.left instanceof AST_Sequence) {
|
||||
var x = this.left.expressions.slice();
|
||||
var e = this.clone();
|
||||
e.left = x.pop();
|
||||
x.push(e);
|
||||
return make_sequence(this, x).optimize(compressor);
|
||||
if (this.left instanceof AST_PropAccess) {
|
||||
if (!(this.left.expression instanceof AST_Sequence)) return this;
|
||||
var x = this.left.expression.expressions.slice();
|
||||
var e = this.clone();
|
||||
e.left = e.left.clone();
|
||||
e.left.expression = x.pop();
|
||||
x.push(e);
|
||||
return make_sequence(this, x);
|
||||
}
|
||||
if (this.left instanceof AST_Sequence) {
|
||||
var x = this.left.expressions.slice();
|
||||
var e = this.clone();
|
||||
e.left = x.pop();
|
||||
x.push(e);
|
||||
return make_sequence(this, x);
|
||||
}
|
||||
if (this.right instanceof AST_Sequence) {
|
||||
if (this.left.has_side_effects(compressor)) return this;
|
||||
var assign = this.operator == "=" && this.left instanceof AST_SymbolRef;
|
||||
var x = this.right.expressions;
|
||||
var last = x.length - 1;
|
||||
for (var i = 0; i < last; i++) {
|
||||
if (!assign && x[i].has_side_effects(compressor)) break;
|
||||
}
|
||||
if (this.right instanceof AST_Sequence && !this.left.has_side_effects(compressor)) {
|
||||
var assign = this.operator == "=" && this.left instanceof AST_SymbolRef;
|
||||
var x = this.right.expressions;
|
||||
var last = x.length - 1;
|
||||
for (var i = 0; i < last; i++) {
|
||||
if (!assign && x[i].has_side_effects(compressor)) break;
|
||||
}
|
||||
if (i == last) {
|
||||
x = x.slice();
|
||||
var e = this.clone();
|
||||
e.right = x.pop();
|
||||
x.push(e);
|
||||
return make_sequence(this, x).optimize(compressor);
|
||||
} else if (i > 0) {
|
||||
var e = this.clone();
|
||||
e.right = make_sequence(this.right, x.slice(i));
|
||||
x = x.slice(0, i);
|
||||
x.push(e);
|
||||
return make_sequence(this, x).optimize(compressor);
|
||||
}
|
||||
if (i == last) {
|
||||
x = x.slice();
|
||||
var e = this.clone();
|
||||
e.right = x.pop();
|
||||
x.push(e);
|
||||
return make_sequence(this, x);
|
||||
}
|
||||
if (i > 0) {
|
||||
var e = this.clone();
|
||||
e.right = make_sequence(this.right, x.slice(i));
|
||||
x = x.slice(0, i);
|
||||
x.push(e);
|
||||
return make_sequence(this, x);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
@@ -6240,8 +6270,10 @@ merge(Compressor.prototype, {
|
||||
// result. hence, force switch.
|
||||
reverse();
|
||||
}
|
||||
var seq = self.lift_sequences(compressor);
|
||||
if (seq !== self) return seq;
|
||||
if (compressor.option("sequences")) {
|
||||
var seq = self.lift_sequences(compressor);
|
||||
if (seq !== self) return seq.optimize(compressor);
|
||||
}
|
||||
if (compressor.option("assignments") && lazy_op[self.operator]) {
|
||||
var assign = self.right;
|
||||
// a || (a = x) => a = a || x
|
||||
@@ -6450,7 +6482,7 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
// (x || false) && y => x ? y : false
|
||||
if (self.left.operator == "||") {
|
||||
var lr = self.left.right.tail_node().evaluate(compressor);
|
||||
var lr = self.left.right.evaluate(compressor, true);
|
||||
if (!lr) return make_node(AST_Conditional, self, {
|
||||
condition: self.left.left,
|
||||
consequent: self.right,
|
||||
@@ -6484,7 +6516,7 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
// x && true || y => x ? true : y
|
||||
if (self.left.operator == "&&") {
|
||||
var lr = self.left.right.is_truthy() || self.left.right.tail_node().evaluate(compressor);
|
||||
var lr = self.left.right.is_truthy() || self.left.right.evaluate(compressor, true);
|
||||
if (lr && !(lr instanceof AST_Node)) return make_node(AST_Conditional, self, {
|
||||
condition: self.left.left,
|
||||
consequent: self.left.right,
|
||||
@@ -6953,9 +6985,9 @@ merge(Compressor.prototype, {
|
||||
var fn = node.fixed_value();
|
||||
if (!(fn instanceof AST_Lambda)) return;
|
||||
if (!fn.name) return;
|
||||
var fn_def = fn.name.definition();
|
||||
if (fn_def.scope !== fn.name.scope) return;
|
||||
if (fixed.variables.get(fn.name.name) !== fn_def) return;
|
||||
if (fn.name.definition() !== def) return;
|
||||
if (def.scope !== fn.name.scope) return;
|
||||
if (fixed.variables.get(fn.name.name) !== def) return;
|
||||
fn.name = fn.name.clone();
|
||||
var value_def = value.variables.get(fn.name.name) || value.def_function(fn.name);
|
||||
node.thedef = value_def;
|
||||
@@ -7158,8 +7190,10 @@ merge(Compressor.prototype, {
|
||||
|| parent instanceof AST_UnaryPrefix);
|
||||
}
|
||||
}
|
||||
var seq = self.lift_sequences(compressor);
|
||||
if (seq !== self) return seq;
|
||||
if (compressor.option("sequences")) {
|
||||
var seq = self.lift_sequences(compressor);
|
||||
if (seq !== self) return seq.optimize(compressor);
|
||||
}
|
||||
if (!compressor.option("assignments")) return self;
|
||||
if (self.operator == "=" && self.left instanceof AST_SymbolRef && self.right instanceof AST_Binary) {
|
||||
// x = expr1 OP expr2
|
||||
@@ -7581,16 +7615,14 @@ merge(Compressor.prototype, {
|
||||
value = value.fixed_value();
|
||||
}
|
||||
if (!value) return false;
|
||||
return !(value instanceof AST_Lambda)
|
||||
|| compressor.parent() instanceof AST_New
|
||||
|| !value.contains_this();
|
||||
if (!(value instanceof AST_Lambda)) return true;
|
||||
var parent = compressor.parent();
|
||||
if (parent.TYPE != "Call") return true;
|
||||
if (parent.expression !== compressor.self()) return true;
|
||||
return !value.contains_this();
|
||||
}
|
||||
|
||||
OPT(AST_Sub, function(self, compressor) {
|
||||
if (compressor.option("sequences") && compressor.parent().TYPE != "Call") {
|
||||
var seq = lift_sequence_in_expression(self, compressor);
|
||||
if (seq !== self) return seq.optimize(compressor);
|
||||
}
|
||||
var expr = self.expression;
|
||||
var prop = self.property;
|
||||
if (compressor.option("properties")) {
|
||||
@@ -7661,6 +7693,10 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
}
|
||||
if (is_lhs(compressor.self(), parent)) return self;
|
||||
if (compressor.option("sequences") && compressor.parent().TYPE != "Call") {
|
||||
var seq = lift_sequence_in_expression(self, compressor);
|
||||
if (seq !== self) return seq.optimize(compressor);
|
||||
}
|
||||
if (key !== prop) {
|
||||
var sub = self.flatten_object(property, compressor);
|
||||
if (sub) {
|
||||
@@ -7755,10 +7791,6 @@ merge(Compressor.prototype, {
|
||||
});
|
||||
|
||||
OPT(AST_Dot, function(self, compressor) {
|
||||
if (compressor.option("sequences") && compressor.parent().TYPE != "Call") {
|
||||
var seq = lift_sequence_in_expression(self, compressor);
|
||||
if (seq !== self) return seq.optimize(compressor);
|
||||
}
|
||||
if (self.property == "arguments" || self.property == "caller") {
|
||||
AST_Node.warn("Function.prototype.{prop} not supported [{file}:{line},{col}]", {
|
||||
prop: self.property,
|
||||
@@ -7768,6 +7800,10 @@ merge(Compressor.prototype, {
|
||||
});
|
||||
}
|
||||
if (is_lhs(compressor.self(), compressor.parent())) return self;
|
||||
if (compressor.option("sequences") && compressor.parent().TYPE != "Call") {
|
||||
var seq = lift_sequence_in_expression(self, compressor);
|
||||
if (seq !== self) return seq.optimize(compressor);
|
||||
}
|
||||
if (compressor.option("unsafe_proto")
|
||||
&& self.expression instanceof AST_Dot
|
||||
&& self.expression.property == "prototype") {
|
||||
|
||||
@@ -783,6 +783,8 @@ function OutputStream(options) {
|
||||
var p = output.parent();
|
||||
if (p instanceof AST_PropAccess && p.expression === this) {
|
||||
var value = this.value;
|
||||
// https://github.com/mishoo/UglifyJS2/issues/115
|
||||
// https://github.com/mishoo/UglifyJS2/pull/1009
|
||||
if (value < 0 || /^0/.test(make_num(value))) {
|
||||
return true;
|
||||
}
|
||||
@@ -1352,11 +1354,18 @@ function OutputStream(options) {
|
||||
DEFPRINT(AST_RegExp, function(self, output) {
|
||||
var regexp = self.value;
|
||||
var str = regexp.toString();
|
||||
var end = str.lastIndexOf("/");
|
||||
if (regexp.raw_source) {
|
||||
str = "/" + regexp.raw_source + str.slice(str.lastIndexOf("/"));
|
||||
str = "/" + regexp.raw_source + str.slice(end);
|
||||
} else if (end == 1) {
|
||||
str = "/(?:)" + str.slice(end);
|
||||
} else if (str.indexOf("/", 1) < end) {
|
||||
str = "/" + str.slice(1, end).replace(/\\\\|[^/]?\//g, function(match) {
|
||||
return match[0] == "\\" ? match : match.slice(0, -1) + "\\/";
|
||||
}) + str.slice(end);
|
||||
}
|
||||
output.print(output.to_utf8(str).replace(/\\(?:\0(?![0-9])|[^\0])/g, function(seq) {
|
||||
switch (seq[1]) {
|
||||
output.print(output.to_utf8(str).replace(/\\(?:\0(?![0-9])|[^\0])/g, function(match) {
|
||||
switch (match[1]) {
|
||||
case "\n": return "\\n";
|
||||
case "\r": return "\\r";
|
||||
case "\t": return "\t";
|
||||
@@ -1366,7 +1375,7 @@ function OutputStream(options) {
|
||||
case "\x0B": return "\v";
|
||||
case "\u2028": return "\\u2028";
|
||||
case "\u2029": return "\\u2029";
|
||||
default: return seq;
|
||||
default: return match;
|
||||
}
|
||||
}).replace(/[\n\r\u2028\u2029]/g, function(c) {
|
||||
switch (c) {
|
||||
|
||||
@@ -52,7 +52,7 @@ TreeTransformer.prototype = new TreeWalker;
|
||||
|
||||
(function(DEF) {
|
||||
function do_list(list, tw) {
|
||||
return MAP(list, function(node) {
|
||||
return List(list, function(node) {
|
||||
return node.transform(tw, true);
|
||||
});
|
||||
}
|
||||
|
||||
20
lib/utils.js
20
lib/utils.js
@@ -113,8 +113,8 @@ function return_true() { return true; }
|
||||
function return_this() { return this; }
|
||||
function return_null() { return null; }
|
||||
|
||||
var MAP = (function() {
|
||||
function MAP(a, f, backwards) {
|
||||
var List = (function() {
|
||||
function List(a, f, backwards) {
|
||||
var ret = [], top = [], i;
|
||||
function doit() {
|
||||
var val = f(a[i], i);
|
||||
@@ -149,14 +149,14 @@ var MAP = (function() {
|
||||
}
|
||||
return top.concat(ret);
|
||||
}
|
||||
MAP.at_top = function(val) { return new AtTop(val) };
|
||||
MAP.splice = function(val) { return new Splice(val) };
|
||||
MAP.last = function(val) { return new Last(val) };
|
||||
var skip = MAP.skip = {};
|
||||
function AtTop(val) { this.v = val }
|
||||
function Splice(val) { this.v = val }
|
||||
function Last(val) { this.v = val }
|
||||
return MAP;
|
||||
List.at_top = function(val) { return new AtTop(val); };
|
||||
List.splice = function(val) { return new Splice(val); };
|
||||
List.last = function(val) { return new Last(val); };
|
||||
var skip = List.skip = {};
|
||||
function AtTop(val) { this.v = val; }
|
||||
function Splice(val) { this.v = val; }
|
||||
function Last(val) { this.v = val; }
|
||||
return List;
|
||||
})();
|
||||
|
||||
function push_uniq(array, el) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"description": "JavaScript parser, mangler/compressor and beautifier toolkit",
|
||||
"author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
|
||||
"license": "BSD-2-Clause",
|
||||
"version": "3.7.7",
|
||||
"version": "3.8.0",
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
},
|
||||
|
||||
@@ -1191,10 +1191,10 @@ issue_2105_1: {
|
||||
input: {
|
||||
!function(factory) {
|
||||
factory();
|
||||
}( function() {
|
||||
}(function() {
|
||||
return function(fn) {
|
||||
fn()().prop();
|
||||
}( function() {
|
||||
}(function() {
|
||||
function bar() {
|
||||
var quux = function() {
|
||||
console.log("PASS");
|
||||
@@ -1205,7 +1205,7 @@ issue_2105_1: {
|
||||
return { prop: foo };
|
||||
}
|
||||
return bar;
|
||||
} );
|
||||
});
|
||||
});
|
||||
}
|
||||
expect: {
|
||||
@@ -1235,10 +1235,10 @@ issue_2105_2: {
|
||||
input: {
|
||||
!function(factory) {
|
||||
factory();
|
||||
}( function() {
|
||||
}(function() {
|
||||
return function(fn) {
|
||||
fn()().prop();
|
||||
}( function() {
|
||||
}(function() {
|
||||
function bar() {
|
||||
var quux = function() {
|
||||
console.log("PASS");
|
||||
@@ -1249,7 +1249,7 @@ issue_2105_2: {
|
||||
return { prop: foo };
|
||||
}
|
||||
return bar;
|
||||
} );
|
||||
});
|
||||
});
|
||||
}
|
||||
expect: {
|
||||
@@ -1258,6 +1258,44 @@ issue_2105_2: {
|
||||
expect_stdout: "PASS"
|
||||
}
|
||||
|
||||
issue_2105_3: {
|
||||
options = {
|
||||
inline: true,
|
||||
passes: 2,
|
||||
reduce_vars: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
!function(factory) {
|
||||
factory();
|
||||
}(function() {
|
||||
return function(fn) {
|
||||
fn()().prop();
|
||||
}(function() {
|
||||
function bar() {
|
||||
var quux = function() {
|
||||
console.log("PASS");
|
||||
}, foo = function() {
|
||||
console.log;
|
||||
quux();
|
||||
};
|
||||
return { prop: foo };
|
||||
}
|
||||
return bar;
|
||||
});
|
||||
});
|
||||
}
|
||||
expect: {
|
||||
!void void {
|
||||
prop: function() {
|
||||
console.log;
|
||||
void console.log("PASS");
|
||||
}
|
||||
}.prop();
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
}
|
||||
|
||||
issue_2226_1: {
|
||||
options = {
|
||||
side_effects: true,
|
||||
@@ -2330,7 +2368,7 @@ function_parameter_ie8: {
|
||||
(function() {
|
||||
(function f() {
|
||||
console.log("PASS");
|
||||
})(0);
|
||||
})();
|
||||
})();
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
|
||||
@@ -1276,7 +1276,7 @@ issue_2630_3: {
|
||||
(function() {
|
||||
(function f1(a) {
|
||||
f2();
|
||||
--x >= 0 && f1({});
|
||||
--x >= 0 && f1();
|
||||
})(a++);
|
||||
function f2() {
|
||||
a++;
|
||||
|
||||
@@ -2361,3 +2361,40 @@ issue_3542: {
|
||||
}
|
||||
expect_stdout: "1"
|
||||
}
|
||||
|
||||
issue_3703: {
|
||||
options = {
|
||||
ie8: true,
|
||||
reduce_vars: true,
|
||||
toplevel: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
var a = "PASS";
|
||||
function f() {
|
||||
var b;
|
||||
function g() {
|
||||
a = "FAIL";
|
||||
}
|
||||
var c = g;
|
||||
function h() {
|
||||
f;
|
||||
}
|
||||
a ? b |= c : b.p;
|
||||
}
|
||||
f();
|
||||
console.log(a);
|
||||
}
|
||||
expect: {
|
||||
var a = "PASS";
|
||||
(function() {
|
||||
var b;
|
||||
var c = function g() {
|
||||
a = "FAIL";
|
||||
};
|
||||
a ? b |= c : b.p;
|
||||
})();
|
||||
console.log(a);
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
}
|
||||
|
||||
@@ -728,7 +728,7 @@ issue_2630_3: {
|
||||
(function() {
|
||||
(function f1() {
|
||||
f2();
|
||||
--x >= 0 && f1({});
|
||||
--x >= 0 && f1();
|
||||
})(a++);
|
||||
function f2() {
|
||||
a++;
|
||||
@@ -1369,7 +1369,7 @@ recursive_iife_1: {
|
||||
}
|
||||
expect: {
|
||||
console.log(function f(a, b) {
|
||||
return b || f("FAIL", "PASS");
|
||||
return b || f(0, "PASS");
|
||||
}());
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
@@ -1388,7 +1388,7 @@ recursive_iife_2: {
|
||||
}
|
||||
expect: {
|
||||
console.log(function f(a, b) {
|
||||
return b || f("FAIL", "PASS");
|
||||
return b || f(0, "PASS");
|
||||
}(0, 0));
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
@@ -1416,7 +1416,7 @@ recursive_iife_3: {
|
||||
var a = 1, c = "PASS";
|
||||
(function() {
|
||||
(function f(b, d, e) {
|
||||
a-- && f(null, 42, 0);
|
||||
a-- && f(0, 42, 0);
|
||||
e && (c = "FAIL");
|
||||
d && d.p;
|
||||
})();
|
||||
|
||||
@@ -817,6 +817,29 @@ issue_2208_5: {
|
||||
expect_stdout: "42"
|
||||
}
|
||||
|
||||
issue_2208_6: {
|
||||
options = {
|
||||
inline: true,
|
||||
properties: true,
|
||||
side_effects: true,
|
||||
}
|
||||
input: {
|
||||
a = 42;
|
||||
console.log(("FAIL", {
|
||||
p: function() {
|
||||
return this.a;
|
||||
}
|
||||
}.p)());
|
||||
}
|
||||
expect: {
|
||||
a = 42;
|
||||
console.log(function() {
|
||||
return this.a;
|
||||
}());
|
||||
}
|
||||
expect_stdout: "42"
|
||||
}
|
||||
|
||||
issue_2256: {
|
||||
options = {
|
||||
side_effects: true,
|
||||
|
||||
@@ -2071,13 +2071,8 @@ issue_1670_6: {
|
||||
}
|
||||
expect: {
|
||||
(function(a) {
|
||||
switch (1) {
|
||||
case a = 1:
|
||||
console.log(a);
|
||||
break;
|
||||
default:
|
||||
console.log(2);
|
||||
}
|
||||
a = 1;
|
||||
console.log(a);
|
||||
})(1);
|
||||
}
|
||||
expect_stdout: "1"
|
||||
|
||||
@@ -186,3 +186,72 @@ issue_3434_3: {
|
||||
/\nfo\n[\n]o\bbb/;
|
||||
}
|
||||
}
|
||||
|
||||
issue_3434_4: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
unsafe: true,
|
||||
}
|
||||
input: {
|
||||
[
|
||||
[ "", RegExp("") ],
|
||||
[ "/", RegExp("/") ],
|
||||
[ "//", RegExp("//") ],
|
||||
[ "\/", RegExp("\\/") ],
|
||||
[ "///", RegExp("///") ],
|
||||
[ "/\/", RegExp("/\\/") ],
|
||||
[ "\//", RegExp("\\//") ],
|
||||
[ "\\/", RegExp("\\\\/") ],
|
||||
[ "////", RegExp("////") ],
|
||||
[ "//\/", RegExp("//\\/") ],
|
||||
[ "/\//", RegExp("/\\//") ],
|
||||
[ "/\\/", RegExp("/\\\\/") ],
|
||||
[ "\///", RegExp("\\///") ],
|
||||
[ "\/\/", RegExp("\\/\\/") ],
|
||||
[ "\\//", RegExp("\\\\//") ],
|
||||
[ "\\\/", RegExp("\\\\\\/") ],
|
||||
].forEach(function(test) {
|
||||
console.log(test[1].test("\\"), test[1].test(test[0]));
|
||||
});
|
||||
}
|
||||
expect: {
|
||||
[
|
||||
[ "", /(?:)/ ],
|
||||
[ "/", /\// ],
|
||||
[ "//", /\/\// ],
|
||||
[ "/", /\// ],
|
||||
[ "///", /\/\/\// ],
|
||||
[ "//", /\/\// ],
|
||||
[ "//", /\/\// ],
|
||||
[ "\\/", /\\\// ],
|
||||
[ "////", /\/\/\/\// ],
|
||||
[ "///", /\/\/\// ],
|
||||
[ "///", /\/\/\// ],
|
||||
[ "/\\/", /\/\\\// ],
|
||||
[ "///", /\/\/\// ],
|
||||
[ "//", /\/\// ],
|
||||
[ "\\//", /\\\/\// ],
|
||||
[ "\\/", /\\\// ],
|
||||
].forEach(function(test) {
|
||||
console.log(test[1].test("\\"), test[1].test(test[0]));
|
||||
});
|
||||
}
|
||||
expect_stdout: [
|
||||
"true true",
|
||||
"false true",
|
||||
"false true",
|
||||
"false true",
|
||||
"false true",
|
||||
"false true",
|
||||
"false true",
|
||||
"false true",
|
||||
"false true",
|
||||
"false true",
|
||||
"false true",
|
||||
"false true",
|
||||
"false true",
|
||||
"false true",
|
||||
"false true",
|
||||
"false true",
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1093,3 +1093,22 @@ issue_3490_2: {
|
||||
}
|
||||
expect_stdout: "PASS 42"
|
||||
}
|
||||
|
||||
issue_3703: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
sequences: true,
|
||||
unsafe: true,
|
||||
}
|
||||
input: {
|
||||
var a = "FAIL";
|
||||
while ((a = "PASS", 0).foo = 0);
|
||||
console.log(a);
|
||||
}
|
||||
expect: {
|
||||
var a = "FAIL";
|
||||
while (a = "PASS", (0).foo = 0);
|
||||
console.log(a);
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
exports["Compressor"] = Compressor;
|
||||
exports["defaults"] = defaults;
|
||||
exports["JS_Parse_Error"] = JS_Parse_Error;
|
||||
exports["List"] = List;
|
||||
exports["mangle_properties"] = mangle_properties;
|
||||
exports["minify"] = minify;
|
||||
exports["OutputStream"] = OutputStream;
|
||||
|
||||
9
test/input/reduce/label.js
Normal file
9
test/input/reduce/label.js
Normal file
@@ -0,0 +1,9 @@
|
||||
var o = this;
|
||||
|
||||
for (var k in o) L17060: {
|
||||
a++;
|
||||
}
|
||||
|
||||
var a;
|
||||
|
||||
console.log(k);
|
||||
16
test/input/reduce/label.reduced.js
Normal file
16
test/input/reduce/label.reduced.js
Normal file
@@ -0,0 +1,16 @@
|
||||
var o = this;
|
||||
|
||||
for (var k in o) {
|
||||
0;
|
||||
}
|
||||
|
||||
var a;
|
||||
|
||||
console.log(k);
|
||||
// output: a
|
||||
//
|
||||
// minify: k
|
||||
//
|
||||
// options: {
|
||||
// "mangle": false
|
||||
// }
|
||||
18
test/input/reduce/unsafe_math.js
Normal file
18
test/input/reduce/unsafe_math.js
Normal file
@@ -0,0 +1,18 @@
|
||||
var _calls_ = 10, a = 100, b = 10, c = 0;
|
||||
|
||||
function f0(b_1, a, undefined_2) {
|
||||
a++ + ++b;
|
||||
{
|
||||
var expr2 = (b + 1 - .1 - .1 - .1 || a || 3).toString();
|
||||
L20778: for (var key2 in expr2) {
|
||||
(c = c + 1) + [ --b + b_1, typeof f0 == "function" && --_calls_ >= 0 && f0(--b + typeof (undefined_2 = 1 === 1 ? a : b), --b + {
|
||||
c: (c = c + 1) + null
|
||||
}, a++ + (typeof f0 == "function" && --_calls_ >= 0 && f0(typeof (c = 1 + c, 3 / "a" * ("c" >>> 23..toString()) >= (b_1 && (b_1[(c = c + 1) + a--] = (- -0,
|
||||
true + {})))), 3, 25))), 1 === 1 ? a : b ];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var a_1 = f0([ , 0 ].length === 2);
|
||||
|
||||
console.log(null, a, b, c, Infinity, NaN, undefined);
|
||||
19
test/input/reduce/unsafe_math.reduced.js
Normal file
19
test/input/reduce/unsafe_math.reduced.js
Normal file
@@ -0,0 +1,19 @@
|
||||
var b = 0;
|
||||
|
||||
var expr2 = (0 - 1 - .1 - .1).toString();
|
||||
|
||||
for (var key2 in expr2) {
|
||||
--b;
|
||||
}
|
||||
|
||||
console.log(b);
|
||||
// output: -19
|
||||
//
|
||||
// minify: -4
|
||||
//
|
||||
// options: {
|
||||
// "compress": {
|
||||
// "unsafe_math": true
|
||||
// },
|
||||
// "mangle": false
|
||||
// }
|
||||
167
test/mocha/reduce.js
Normal file
167
test/mocha/reduce.js
Normal file
@@ -0,0 +1,167 @@
|
||||
var assert = require("assert");
|
||||
var exec = require("child_process").exec;
|
||||
var fs = require("fs");
|
||||
var reduce_test = require("../reduce");
|
||||
var semver = require("semver");
|
||||
|
||||
function read(path) {
|
||||
return fs.readFileSync(path, "utf8");
|
||||
}
|
||||
|
||||
describe("test/reduce.js", function() {
|
||||
this.timeout(60000);
|
||||
it("Should reduce test case", function() {
|
||||
var result = reduce_test(read("test/input/reduce/unsafe_math.js"), {
|
||||
compress: {
|
||||
unsafe_math: true,
|
||||
},
|
||||
mangle: false,
|
||||
}, {
|
||||
verbose: false,
|
||||
});
|
||||
if (result.error) throw result.error;
|
||||
assert.strictEqual(result.code, read("test/input/reduce/unsafe_math.reduced.js"));
|
||||
});
|
||||
it("Should eliminate unreferenced labels", function() {
|
||||
var result = reduce_test(read("test/input/reduce/label.js"), {
|
||||
mangle: false,
|
||||
}, {
|
||||
verbose: false,
|
||||
});
|
||||
if (result.error) throw result.error;
|
||||
assert.strictEqual(result.code, read("test/input/reduce/label.reduced.js"));
|
||||
});
|
||||
it("Should handle test cases with --toplevel", function() {
|
||||
var result = reduce_test([
|
||||
"var Infinity = 42;",
|
||||
"console.log(Infinity);",
|
||||
].join("\n"), {
|
||||
toplevel: true,
|
||||
});
|
||||
if (result.error) throw result.error;
|
||||
assert.strictEqual(result.code, [
|
||||
"// Can't reproduce test failure with minify options provided:",
|
||||
"// {",
|
||||
'// "toplevel": true',
|
||||
"// }",
|
||||
].join("\n"));
|
||||
});
|
||||
it("Should handle test result of NaN", function() {
|
||||
var result = reduce_test("throw 0 / 0;");
|
||||
if (result.error) throw result.error;
|
||||
assert.strictEqual(result.code, [
|
||||
"// Can't reproduce test failure with minify options provided:",
|
||||
"// {",
|
||||
'// "compress": {},',
|
||||
'// "mangle": false',
|
||||
"// }",
|
||||
].join("\n"));
|
||||
});
|
||||
it("Should print correct output for irreducible test case", function() {
|
||||
var result = reduce_test([
|
||||
"console.log(function f(a) {",
|
||||
" return f.length;",
|
||||
"}());",
|
||||
].join("\n"), {
|
||||
compress: {
|
||||
keep_fargs: false,
|
||||
},
|
||||
mangle: false,
|
||||
});
|
||||
if (result.error) throw result.error;
|
||||
assert.strictEqual(result.code, [
|
||||
"console.log(function f(a) {",
|
||||
" return f.length;",
|
||||
"}());",
|
||||
"// output: 1",
|
||||
"// ",
|
||||
"// minify: 0",
|
||||
"// ",
|
||||
"// options: {",
|
||||
'// "compress": {',
|
||||
'// "keep_fargs": false',
|
||||
"// },",
|
||||
'// "mangle": false',
|
||||
"// }",
|
||||
].join("\n"));
|
||||
});
|
||||
it("Should fail when invalid option is supplied", function() {
|
||||
var result = reduce_test("", {
|
||||
compress: {
|
||||
unsafe_regex: true,
|
||||
},
|
||||
});
|
||||
var err = result.error;
|
||||
assert.ok(err instanceof Error);
|
||||
assert.strictEqual(err.stack.split(/\n/)[0], "DefaultsError: `unsafe_regex` is not a supported option");
|
||||
});
|
||||
it("Should report on test case with invalid syntax", function() {
|
||||
var result = reduce_test("var 0 = 1;");
|
||||
var err = result.error;
|
||||
assert.ok(err instanceof Error);
|
||||
assert.strictEqual(err.stack.split(/\n/)[0], "SyntaxError: Name expected");
|
||||
});
|
||||
it("Should format multi-line output correctly", function() {
|
||||
var code = [
|
||||
"var a = 0;",
|
||||
"",
|
||||
"for (var b in [ 1, 2, 3 ]) {",
|
||||
" a = +a + 1 - .2;",
|
||||
" console.log(a);",
|
||||
"}",
|
||||
].join("\n");
|
||||
var result = reduce_test(code, {
|
||||
compress: {
|
||||
unsafe_math: true,
|
||||
},
|
||||
mangle: false,
|
||||
});
|
||||
if (result.error) throw result.error;
|
||||
assert.strictEqual(result.code, [
|
||||
code,
|
||||
"// output: 0.8",
|
||||
"// 1.6",
|
||||
"// 2.4",
|
||||
"// ",
|
||||
"// minify: 0.8",
|
||||
"// 1.6",
|
||||
"// 2.4000000000000004",
|
||||
"// ",
|
||||
"// options: {",
|
||||
'// "compress": {',
|
||||
'// "unsafe_math": true',
|
||||
"// },",
|
||||
'// "mangle": false',
|
||||
"// }",
|
||||
].join("\n"));
|
||||
});
|
||||
it("Should reduce infinite loops with reasonable performance", function() {
|
||||
if (semver.satisfies(process.version, "0.10")) return;
|
||||
this.timeout(120000);
|
||||
var code = [
|
||||
"var a = 9007199254740992, b = 1;",
|
||||
"",
|
||||
"while (a++ + (1 - b) < a) {",
|
||||
" 0;",
|
||||
"}",
|
||||
].join("\n");
|
||||
var result = reduce_test(code, {
|
||||
compress: {
|
||||
unsafe_math: true,
|
||||
},
|
||||
mangle: false,
|
||||
});
|
||||
if (result.error) throw result.error;
|
||||
assert.strictEqual(result.code.replace(/ timed out after [0-9]+ms/, " timed out."), [
|
||||
code,
|
||||
"// output: ",
|
||||
"// minify: Error: Script execution timed out.",
|
||||
"// options: {",
|
||||
'// "compress": {',
|
||||
'// "unsafe_math": true',
|
||||
"// },",
|
||||
'// "mangle": false',
|
||||
"// }",
|
||||
].join("\n"));
|
||||
});
|
||||
});
|
||||
564
test/reduce.js
Normal file
564
test/reduce.js
Normal file
@@ -0,0 +1,564 @@
|
||||
var crypto = require("crypto");
|
||||
var U = require("./node");
|
||||
var List = U.List;
|
||||
var os = require("os");
|
||||
var sandbox = require("./sandbox");
|
||||
|
||||
// Reduce a ufuzz-style `console.log` based test case by iteratively replacing
|
||||
// AST nodes with various permutations. Each AST_Statement in the tree is also
|
||||
// speculatively dropped to determine whether it is needed. If the altered
|
||||
// tree and the last known good tree produce the same non-nil error-free output
|
||||
// after being run, then the permutation survives to the next generation and
|
||||
// is the basis for subsequent iterations. The test case is reduced as a
|
||||
// consequence of complex expressions being replaced with simpler ones.
|
||||
// This function assumes that the testcase will not result in a parse or
|
||||
// runtime Error. Note that a reduced test case will have different runtime
|
||||
// output - it is not functionally equivalent to the original. The only criteria
|
||||
// is that once the generated reduced test case is run without minification, it
|
||||
// will produce different output from the code minified with `minify_options`.
|
||||
// Returns a `minify` result object with an additonal boolean property `reduced`.
|
||||
|
||||
module.exports = function reduce_test(testcase, minify_options, reduce_options) {
|
||||
if (testcase instanceof U.AST_Node) testcase = testcase.print_to_string();
|
||||
minify_options = minify_options || { compress: {}, mangle: false };
|
||||
reduce_options = reduce_options || {};
|
||||
var max_iterations = reduce_options.max_iterations || 1000;
|
||||
var max_timeout = reduce_options.max_timeout || 10000;
|
||||
var verbose = reduce_options.verbose;
|
||||
var minify_options_json = JSON.stringify(minify_options, null, 2);
|
||||
var result_cache = Object.create(null);
|
||||
// the initial timeout to assess the viability of the test case must be large
|
||||
var differs = producesDifferentResultWhenMinified(result_cache, testcase, minify_options, max_timeout);
|
||||
|
||||
if (verbose) {
|
||||
console.error("// Node.js " + process.version + " on " + os.platform() + " " + os.arch());
|
||||
}
|
||||
if (!differs) {
|
||||
// same stdout result produced when minified
|
||||
return {
|
||||
code: "// Can't reproduce test failure with minify options provided:"
|
||||
+ "\n// " + to_comment(minify_options_json)
|
||||
};
|
||||
} else if (differs.timed_out) {
|
||||
return {
|
||||
code: "// Can't reproduce test failure within " + max_timeout + "ms:"
|
||||
+ "\n// " + to_comment(minify_options_json)
|
||||
};
|
||||
} else if (differs.error) {
|
||||
return differs;
|
||||
} else {
|
||||
max_timeout = Math.min(100 * differs.elapsed, max_timeout);
|
||||
// Replace expressions with constants that will be parsed into
|
||||
// AST_Nodes as required. Each AST_Node has its own permutation count,
|
||||
// so these replacements can't be shared.
|
||||
// Although simpler replacements are generally faster and better,
|
||||
// feel free to experiment with a different replacement set.
|
||||
var REPLACEMENTS = [
|
||||
// "null", "''", "false", "'foo'", "undefined", "9",
|
||||
"1", "0",
|
||||
];
|
||||
|
||||
// There's a relationship between each node's _permute counter and
|
||||
// REPLACEMENTS.length which is why fractional _permutes were needed.
|
||||
// One could scale all _permute operations by a factor of `steps`
|
||||
// to only deal with integer operations, but this works well enough.
|
||||
var steps = 4; // must be a power of 2
|
||||
var step = 1 / steps; // 0.25 is exactly representable in floating point
|
||||
|
||||
var tt = new U.TreeTransformer(function(node, descend, in_list) {
|
||||
if (CHANGED) return;
|
||||
|
||||
// quick ignores
|
||||
if (node instanceof U.AST_Accessor) return;
|
||||
if (node instanceof U.AST_Directive) return;
|
||||
if (node instanceof U.AST_Label) return;
|
||||
if (node instanceof U.AST_LabelRef) return;
|
||||
if (!in_list && node instanceof U.AST_SymbolDeclaration) return;
|
||||
if (node instanceof U.AST_Toplevel) return;
|
||||
|
||||
var parent = tt.parent();
|
||||
|
||||
// ensure that the _permute prop is a number.
|
||||
// can not use `node.start._permute |= 0;` as it will erase fractional part.
|
||||
if (typeof node.start._permute === "undefined") node.start._permute = 0;
|
||||
|
||||
// if node reached permutation limit - skip over it.
|
||||
// no structural AST changes before this point.
|
||||
if (node.start._permute >= REPLACEMENTS.length) return;
|
||||
|
||||
if (parent instanceof U.AST_Assign
|
||||
&& parent.left === node
|
||||
|| parent instanceof U.AST_Unary
|
||||
&& parent.expression === node
|
||||
&& ["++", "--", "delete"].indexOf(parent.operator) >= 0) {
|
||||
// ignore lvalues
|
||||
return;
|
||||
}
|
||||
if ((parent instanceof U.AST_For || parent instanceof U.AST_ForIn)
|
||||
&& parent.init === node && node instanceof U.AST_Var) {
|
||||
// preserve for (var ...)
|
||||
return node;
|
||||
}
|
||||
|
||||
// node specific permutations with no parent logic
|
||||
|
||||
if (node instanceof U.AST_Array) {
|
||||
var expr = node.elements[0];
|
||||
if (expr && !(expr instanceof U.AST_Hole)) {
|
||||
node.start._permute++;
|
||||
CHANGED = true;
|
||||
return expr;
|
||||
}
|
||||
}
|
||||
else if (node instanceof U.AST_Binary) {
|
||||
CHANGED = true;
|
||||
return [
|
||||
node.left,
|
||||
node.right,
|
||||
][ ((node.start._permute += step) * steps | 0) % 2 ];
|
||||
}
|
||||
else if (node instanceof U.AST_Catch || node instanceof U.AST_Finally) {
|
||||
// drop catch or finally block
|
||||
node.start._permute++;
|
||||
CHANGED = true;
|
||||
return null;
|
||||
}
|
||||
else if (node instanceof U.AST_Conditional) {
|
||||
CHANGED = true;
|
||||
return [
|
||||
node.condition,
|
||||
node.consequent,
|
||||
node.alternative,
|
||||
][ ((node.start._permute += step) * steps | 0) % 3 ];
|
||||
}
|
||||
else if (node instanceof U.AST_BlockStatement) {
|
||||
if (in_list) {
|
||||
node.start._permute++;
|
||||
CHANGED = true;
|
||||
return List.splice(node.body);
|
||||
}
|
||||
}
|
||||
else if (node instanceof U.AST_Call) {
|
||||
var expr = [
|
||||
node.expression,
|
||||
node.args[0],
|
||||
null, // intentional
|
||||
][ ((node.start._permute += step) * steps | 0) % 3 ];
|
||||
if (expr) {
|
||||
CHANGED = true;
|
||||
return expr;
|
||||
}
|
||||
if (node.expression instanceof U.AST_Function) {
|
||||
// hoist and return expressions from the IIFE function expression
|
||||
var body = node.expression.body;
|
||||
node.expression.body = [];
|
||||
var seq = [];
|
||||
body.forEach(function(node) {
|
||||
var expr = expr instanceof U.AST_Exit ? node.value : node.body;
|
||||
if (expr instanceof U.AST_Node && !is_statement(expr)) {
|
||||
// collect expressions from each statements' body
|
||||
seq.push(expr);
|
||||
}
|
||||
});
|
||||
CHANGED = true;
|
||||
return to_sequence(seq);
|
||||
}
|
||||
}
|
||||
else if (node instanceof U.AST_Defun) {
|
||||
switch (((node.start._permute += step) * steps | 0) % 2) {
|
||||
case 0:
|
||||
CHANGED = true;
|
||||
return List.skip;
|
||||
case 1:
|
||||
if (!has_exit(node)) {
|
||||
// hoist function declaration body
|
||||
var body = node.body;
|
||||
node.body = [];
|
||||
body.push(node); // retain function with empty body to be dropped later
|
||||
CHANGED = true;
|
||||
return List.splice(body);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (node instanceof U.AST_DWLoop) {
|
||||
var expr = [
|
||||
node.condition,
|
||||
node.body,
|
||||
null, // intentional
|
||||
][ (node.start._permute * steps | 0) % 3 ];
|
||||
node.start._permute += step;
|
||||
if (!expr) {
|
||||
if (node.body[0] instanceof U.AST_Break) {
|
||||
if (node instanceof U.AST_Do) {
|
||||
CHANGED = true;
|
||||
return List.skip;
|
||||
}
|
||||
expr = node.condition; // AST_While - fall through
|
||||
}
|
||||
}
|
||||
if (expr && (expr !== node.body || !has_loopcontrol(expr, node, parent))) {
|
||||
CHANGED = true;
|
||||
return to_statement(expr);
|
||||
}
|
||||
}
|
||||
else if (node instanceof U.AST_PropAccess) {
|
||||
var expr = [
|
||||
node.expression,
|
||||
node.property instanceof U.AST_Node && node.property,
|
||||
][ node.start._permute++ % 2 ];
|
||||
if (expr) {
|
||||
CHANGED = true;
|
||||
return expr;
|
||||
}
|
||||
}
|
||||
else if (node instanceof U.AST_For) {
|
||||
var expr = [
|
||||
node.init,
|
||||
node.condition,
|
||||
node.step,
|
||||
node.body,
|
||||
][ (node.start._permute * steps | 0) % 4 ];
|
||||
node.start._permute += step;
|
||||
if (expr && (expr !== node.body || !has_loopcontrol(expr, node, parent))) {
|
||||
CHANGED = true;
|
||||
return to_statement(expr);
|
||||
}
|
||||
}
|
||||
else if (node instanceof U.AST_ForIn) {
|
||||
var expr = [
|
||||
node.init,
|
||||
node.object,
|
||||
node.body,
|
||||
][ (node.start._permute * steps | 0) % 3 ];
|
||||
node.start._permute += step;
|
||||
if (expr && (expr !== node.body || !has_loopcontrol(expr, node, parent))) {
|
||||
CHANGED = true;
|
||||
return to_statement(expr);
|
||||
}
|
||||
}
|
||||
else if (node instanceof U.AST_If) {
|
||||
var expr = [
|
||||
node.condition,
|
||||
node.body,
|
||||
node.alternative,
|
||||
][ (node.start._permute * steps | 0) % 3 ];
|
||||
node.start._permute += step;
|
||||
if (expr) {
|
||||
// replace if statement with its condition, then block or else block
|
||||
CHANGED = true;
|
||||
return to_statement(expr);
|
||||
}
|
||||
}
|
||||
else if (node instanceof U.AST_Object) {
|
||||
// first property's value
|
||||
var expr = node.properties[0] instanceof U.AST_ObjectKeyVal && node.properties[0].value;
|
||||
if (expr) {
|
||||
node.start._permute++;
|
||||
CHANGED = true;
|
||||
return expr;
|
||||
}
|
||||
}
|
||||
else if (node instanceof U.AST_SimpleStatement) {
|
||||
if (node.body instanceof U.AST_Call && node.body.expression instanceof U.AST_Function) {
|
||||
// hoist simple statement IIFE function expression body
|
||||
node.start._permute++;
|
||||
if (!has_exit(node.body.expression)) {
|
||||
var body = node.body.expression.body;
|
||||
node.body.expression.body = [];
|
||||
CHANGED = true;
|
||||
return List.splice(body);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (node instanceof U.AST_Switch) {
|
||||
var expr = [
|
||||
node.expression, // switch expression
|
||||
node.body[0] && node.body[0].expression, // first case expression or undefined
|
||||
node.body[0] && node.body[0], // first case body or undefined
|
||||
][ (node.start._permute * steps | 0) % 4 ];
|
||||
node.start._permute += step;
|
||||
if (expr && (!(expr instanceof U.AST_Statement) || !has_loopcontrol(expr, node, parent))) {
|
||||
CHANGED = true;
|
||||
return expr instanceof U.AST_SwitchBranch ? new U.AST_BlockStatement({
|
||||
body: expr.body.slice(),
|
||||
start: {},
|
||||
}) : to_statement(expr);
|
||||
}
|
||||
}
|
||||
else if (node instanceof U.AST_Try) {
|
||||
var body = [
|
||||
node.body,
|
||||
node.bcatch && node.bcatch.body,
|
||||
node.bfinally && node.bfinally.body,
|
||||
null, // intentional
|
||||
][ (node.start._permute * steps | 0) % 4 ];
|
||||
node.start._permute += step;
|
||||
if (body) {
|
||||
// replace try statement with try block, catch block, or finally block
|
||||
CHANGED = true;
|
||||
return new U.AST_BlockStatement({
|
||||
body: body,
|
||||
start: {},
|
||||
});
|
||||
} else {
|
||||
// replace try with a break or return if first in try statement
|
||||
if (node.body[0] instanceof U.AST_Break
|
||||
|| node.body[0] instanceof U.AST_Return) {
|
||||
CHANGED = true;
|
||||
return node.body[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (node instanceof U.AST_Unary) {
|
||||
node.start._permute++;
|
||||
CHANGED = true;
|
||||
return node.expression;
|
||||
}
|
||||
else if (node instanceof U.AST_Var) {
|
||||
if (node.definitions.length == 1 && node.definitions[0].value) {
|
||||
// first declaration value
|
||||
node.start._permute++;
|
||||
CHANGED = true;
|
||||
return to_statement(node.definitions[0].value);
|
||||
}
|
||||
}
|
||||
else if (node instanceof U.AST_LabeledStatement) {
|
||||
if (node.body instanceof U.AST_Statement
|
||||
&& !has_loopcontrol(node.body, node.body, node)) {
|
||||
// replace labelled statement with its non-labelled body
|
||||
node.start._permute = REPLACEMENTS.length;
|
||||
CHANGED = true;
|
||||
return node.body;
|
||||
}
|
||||
}
|
||||
|
||||
if (in_list) {
|
||||
// special case to drop object properties and switch branches
|
||||
if (parent instanceof U.AST_Object
|
||||
|| parent instanceof U.AST_Switch && parent.expression != node) {
|
||||
node.start._permute++;
|
||||
CHANGED = true;
|
||||
return List.skip;
|
||||
}
|
||||
|
||||
// replace or skip statement
|
||||
if (node instanceof U.AST_Statement) {
|
||||
node.start._permute++;
|
||||
CHANGED = true;
|
||||
return List.skip;
|
||||
}
|
||||
|
||||
// remove this node unless its the sole element of a (transient) sequence
|
||||
if (!(parent instanceof U.AST_Sequence) || parent.expressions.length > 1) {
|
||||
node.start._permute++;
|
||||
CHANGED = true;
|
||||
return List.skip;
|
||||
}
|
||||
}
|
||||
|
||||
// replace this node
|
||||
var newNode = U.parse(REPLACEMENTS[node.start._permute % REPLACEMENTS.length | 0], {
|
||||
expression: true,
|
||||
});
|
||||
if (is_statement(node)) {
|
||||
newNode = new U.AST_SimpleStatement({
|
||||
body: newNode,
|
||||
start: {},
|
||||
});
|
||||
}
|
||||
newNode.start._permute = ++node.start._permute;
|
||||
CHANGED = true;
|
||||
return newNode;
|
||||
}, function(node, in_list) {
|
||||
if (node instanceof U.AST_Sequence) {
|
||||
// expand single-element sequence
|
||||
if (node.expressions.length == 1) return node.expressions[0];
|
||||
}
|
||||
else if (node instanceof U.AST_Try) {
|
||||
// expand orphaned try block
|
||||
if (!node.bcatch && !node.bfinally) return new U.AST_BlockStatement({
|
||||
body: node.body,
|
||||
start: {},
|
||||
});
|
||||
}
|
||||
else if (node instanceof U.AST_Var) {
|
||||
// remove empty var statement
|
||||
if (node.definitions.length == 0) return in_list ? List.skip : new U.AST_EmptyStatement({
|
||||
start: {},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
for (var pass = 1; pass <= 3; ++pass) {
|
||||
var testcase_ast = U.parse(testcase);
|
||||
testcase_ast.walk(new U.TreeWalker(function(node) {
|
||||
// unshare start props to retain visit data between iterations
|
||||
node.start = JSON.parse(JSON.stringify(node.start));
|
||||
node.start._permute = 0;
|
||||
}));
|
||||
for (var c = 0; c < max_iterations; ++c) {
|
||||
if (verbose) {
|
||||
if (pass == 1 && c % 25 == 0) {
|
||||
console.error("// reduce test pass "
|
||||
+ pass + ", iteration " + c + ": " + testcase.length + " bytes");
|
||||
}
|
||||
}
|
||||
var CHANGED = false;
|
||||
var code_ast = testcase_ast.clone(true).transform(tt);
|
||||
if (!CHANGED) break;
|
||||
try {
|
||||
var code = code_ast.print_to_string();
|
||||
} catch (ex) {
|
||||
// AST is not well formed.
|
||||
// no harm done - just log the error, ignore latest change and continue iterating.
|
||||
console.error("*** Error generating code from AST.");
|
||||
console.error(ex);
|
||||
console.error("*** Discarding permutation and continuing.");
|
||||
continue;
|
||||
}
|
||||
var diff = producesDifferentResultWhenMinified(result_cache, code, minify_options, max_timeout);
|
||||
if (diff) {
|
||||
if (diff.timed_out) {
|
||||
// can't trust the validity of `code_ast` and `code` when timed out.
|
||||
// no harm done - just ignore latest change and continue iterating.
|
||||
} else if (diff.error) {
|
||||
// something went wrong during minify() - could be malformed AST or genuine bug.
|
||||
// no harm done - just log code & error, ignore latest change and continue iterating.
|
||||
console.error("*** Error during minification.");
|
||||
console.error(code);
|
||||
console.error(diff.error);
|
||||
console.error("*** Discarding permutation and continuing.");
|
||||
} else if (is_error(diff.unminified_result)
|
||||
&& is_error(diff.minified_result)
|
||||
&& diff.unminified_result.name == diff.minified_result.name) {
|
||||
// ignore difference in error messages caused by minification
|
||||
} else {
|
||||
// latest permutation is valid, so use it as the basis of new changes
|
||||
testcase_ast = code_ast;
|
||||
testcase = code;
|
||||
differs = diff;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (c == 0) break;
|
||||
if (verbose) {
|
||||
console.error("// reduce test pass " + pass + ": " + testcase.length + " bytes");
|
||||
}
|
||||
}
|
||||
testcase = U.minify(testcase, {
|
||||
compress: false,
|
||||
mangle: false,
|
||||
output: {
|
||||
beautify: true,
|
||||
braces: true,
|
||||
comments: true,
|
||||
},
|
||||
});
|
||||
testcase.code += [
|
||||
"",
|
||||
"// output: " + to_comment(differs.unminified_result),
|
||||
"// minify: " + to_comment(differs.minified_result),
|
||||
"// options: " + to_comment(minify_options_json),
|
||||
].join("\n").replace(/\u001b\[\d+m/g, "");
|
||||
return testcase;
|
||||
}
|
||||
};
|
||||
|
||||
function to_comment(value) {
|
||||
return ("" + value).replace(/\n/g, "\n// ");
|
||||
}
|
||||
|
||||
function has_exit(fn) {
|
||||
var found = false;
|
||||
var tw = new U.TreeWalker(function(node) {
|
||||
if (found) return found;
|
||||
if (node instanceof U.AST_Exit) {
|
||||
return found = true;
|
||||
}
|
||||
if (node instanceof U.AST_Scope && node !== fn) {
|
||||
return true; // don't descend into nested functions
|
||||
}
|
||||
});
|
||||
fn.walk(tw);
|
||||
return found;
|
||||
}
|
||||
|
||||
function has_loopcontrol(body, loop, label) {
|
||||
var found = false;
|
||||
var tw = new U.TreeWalker(function(node) {
|
||||
if (found) return true;
|
||||
if (node instanceof U.AST_LoopControl && this.loopcontrol_target(node) === loop) {
|
||||
return found = true;
|
||||
}
|
||||
});
|
||||
if (label instanceof U.AST_LabeledStatement) tw.push(label);
|
||||
tw.push(loop);
|
||||
body.walk(tw);
|
||||
return found;
|
||||
}
|
||||
|
||||
function is_error(result) {
|
||||
return typeof result == "object" && typeof result.name == "string" && typeof result.message == "string";
|
||||
}
|
||||
|
||||
function is_timed_out(result) {
|
||||
return is_error(result) && /timed out/.test(result);
|
||||
}
|
||||
|
||||
function is_statement(node) {
|
||||
return node instanceof U.AST_Statement && !(node instanceof U.AST_Function);
|
||||
}
|
||||
|
||||
function merge_sequence(array, node) {
|
||||
if (node instanceof U.AST_Sequence) {
|
||||
array.push.apply(array, node.expressions);
|
||||
} else {
|
||||
array.push(node);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
function to_sequence(expressions) {
|
||||
if (expressions.length == 0) return new U.AST_Number({value: 0, start: {}});
|
||||
if (expressions.length == 1) return expressions[0];
|
||||
return new U.AST_Sequence({
|
||||
expressions: expressions.reduce(merge_sequence, []),
|
||||
start: {},
|
||||
});
|
||||
}
|
||||
|
||||
function to_statement(node) {
|
||||
return is_statement(node) ? node : new U.AST_SimpleStatement({
|
||||
body: node,
|
||||
start: {},
|
||||
});
|
||||
}
|
||||
|
||||
function run_code(result_cache, code, toplevel, timeout) {
|
||||
var key = crypto.createHash("sha1").update(code).digest("base64");
|
||||
return result_cache[key] || (result_cache[key] = sandbox.run_code(code, toplevel, timeout));
|
||||
}
|
||||
|
||||
function producesDifferentResultWhenMinified(result_cache, code, minify_options, max_timeout) {
|
||||
var minified = U.minify(code, minify_options);
|
||||
if (minified.error) return minified;
|
||||
|
||||
var toplevel = minify_options.toplevel;
|
||||
var elapsed = Date.now();
|
||||
var unminified_result = run_code(result_cache, code, toplevel, max_timeout);
|
||||
elapsed = Date.now() - elapsed;
|
||||
var timeout = Math.min(100 * elapsed, max_timeout);
|
||||
var minified_result = run_code(result_cache, minified.code, toplevel, timeout);
|
||||
|
||||
if (sandbox.same_stdout(unminified_result, minified_result)) {
|
||||
return is_timed_out(unminified_result) && is_timed_out(minified_result) && {
|
||||
timed_out: true,
|
||||
};
|
||||
}
|
||||
return {
|
||||
unminified_result: unminified_result,
|
||||
minified_result: minified_result,
|
||||
elapsed: elapsed,
|
||||
};
|
||||
}
|
||||
Error.stackTraceLimit = Infinity;
|
||||
@@ -54,14 +54,15 @@ function createContext() {
|
||||
}
|
||||
}
|
||||
|
||||
exports.run_code = function(code, toplevel) {
|
||||
exports.run_code = function(code, toplevel, timeout) {
|
||||
timeout = timeout || 5000;
|
||||
var stdout = "";
|
||||
var original_write = process.stdout.write;
|
||||
process.stdout.write = function(chunk) {
|
||||
stdout += chunk;
|
||||
};
|
||||
try {
|
||||
vm.runInContext(toplevel ? "(function(){" + code + "})()" : code, createContext(), { timeout: 5000 });
|
||||
vm.runInContext(toplevel ? "(function(){" + code + "})()" : code, createContext(), { timeout: timeout });
|
||||
return stdout;
|
||||
} catch (ex) {
|
||||
return ex;
|
||||
@@ -76,8 +77,9 @@ function strip_func_ids(text) {
|
||||
|
||||
exports.same_stdout = semver.satisfies(process.version, "0.12") ? function(expected, actual) {
|
||||
if (typeof expected != typeof actual) return false;
|
||||
if (typeof expected != "string") {
|
||||
if (expected.name != actual.name) return false;
|
||||
if (typeof expected == "object" && typeof expected.name == "string" && typeof expected.message == "string") {
|
||||
if (expected.name !== actual.name) return false;
|
||||
if (typeof actual.message != "string") return false;
|
||||
expected = expected.message.slice(expected.message.lastIndexOf("\n") + 1);
|
||||
actual = actual.message.slice(actual.message.lastIndexOf("\n") + 1);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ require("../../tools/exit");
|
||||
var UglifyJS = require("../..");
|
||||
var randomBytes = require("crypto").randomBytes;
|
||||
var sandbox = require("../sandbox");
|
||||
var reduce_test = require("../reduce");
|
||||
|
||||
var MAX_GENERATED_TOPLEVELS_PER_RUN = 1;
|
||||
var MAX_GENERATION_RECURSION_DEPTH = 12;
|
||||
@@ -1051,6 +1052,7 @@ function log_rename(options) {
|
||||
}
|
||||
|
||||
function log(options) {
|
||||
var options_copy = JSON.parse(options);
|
||||
options = JSON.parse(options);
|
||||
if (!ok) errorln("\n\n\n\n\n\n!!!!!!!!!!\n\n\n");
|
||||
errorln("//=============================================================");
|
||||
@@ -1069,6 +1071,18 @@ function log(options) {
|
||||
errorln(original_result);
|
||||
errorln("uglified result:");
|
||||
errorln(uglify_result);
|
||||
errorln("//-------------------------------------------------------------");
|
||||
var reduced = reduce_test(original_code, options_copy, {
|
||||
verbose: false,
|
||||
}).code;
|
||||
if (reduced) {
|
||||
errorln();
|
||||
errorln("// reduced test case (output will differ)");
|
||||
errorln();
|
||||
errorln(reduced);
|
||||
errorln();
|
||||
errorln("//-------------------------------------------------------------");
|
||||
}
|
||||
} else {
|
||||
errorln("// !!! uglify failed !!!");
|
||||
errorln(uglify_code);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
exports["Dictionary"] = Dictionary;
|
||||
exports["List"] = List;
|
||||
exports["minify"] = minify;
|
||||
exports["parse"] = parse;
|
||||
exports["push_uniq"] = push_uniq;
|
||||
|
||||
Reference in New Issue
Block a user