Compare commits

...

16 Commits

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

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

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

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

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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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