Compare commits

...

23 Commits

Author SHA1 Message Date
Alex Lam S.L
a5d62a3fc6 v2.8.7 2017-03-05 17:17:08 +08:00
Alex Lam S.L
067e5a5762 fixup for #1553 (#1555)
- `++a` is the one that is foldable
- transform `a++` into `++a` for better optimisation
2017-03-05 17:15:37 +08:00
Alex Lam S.L
33b5f31984 v2.8.6 2017-03-05 15:48:28 +08:00
Alex Lam S.L
35a849dc48 collapse assignment with adjacent subsequent usage (#1553)
- consolidate `cascade` optimisations
- support ++/-- postfixes
- remove redundant optimisation identified in #1460

fixes #368
2017-03-05 14:56:14 +08:00
Alex Lam S.L
b70591be1a handle variable declaration within catch blocks (#1546)
accounts for IE8- scoping
2017-03-05 13:13:44 +08:00
Alex Lam S.L
b33e7f88e6 improve unsafe on undefined (#1548)
`unsafe` turns undefined keyword into a variable of the same name if found, but that interferes with other related optimisations.

Keep track of such transformations to ensure zero information loss in the process.
2017-03-05 13:09:27 +08:00
Alex Lam S.L
1f0333e9f1 stay safe with constants in IE8- (#1547)
- `undefined` etc. can be redefined at top-level for IE8-, so disable related optimisations
- fixed `--support-ie8` catch mangle bug
2017-03-05 12:51:11 +08:00
Alex Lam S.L
eb98a7f2f3 fix handling of shebang and preamble (#1545)
fixes #1332
2017-03-05 12:16:02 +08:00
Alex Lam S.L
78d1bb92d4 fix a corner case in #1530 (#1552) 2017-03-05 12:12:59 +08:00
Alex Lam S.L
ea9ab9fb0e resolve issue with outdated version of async (#1549)
fixes #746
2017-03-05 01:54:20 +08:00
kzc
ce54c9ccee disallow collapse_vars constant replacement in for-in statements (#1543) 2017-03-04 02:39:54 +08:00
Alex Lam S.L
07accd2fbb process code with implicit return statement (#1522)
Bookmarklet for instance implicitedly assumes a "completion value" without using `return`.
The `expression` option now supports such use cases.
Optimisations on IIFEs also enhanced.

fixes #354
fixes #543
fixes #625
fixes #628
fixes #640
closes #1293
2017-03-03 18:13:07 +08:00
Alex Lam S.L
18059cc94f compress numerical expressions (#1513)
safe operations
- `a === b` => `a == b`
- `a + -b`  => `a - b`
- `-a + b`  => `b - a`
- `a+ +b`   => `+b+a`

associative operations
(bit-wise operations are safe, otherwise `unsafe_math`)
- `a + (b + c)`       => `(a + b) + c`
- `(n + 2) + 3`       => `5 + n`
- `(2 * n) * 3`       => `6 * n`
- `(a | 1) | (2 | d)` => `(3 | a) | b`

fixes #412
2017-03-03 18:04:32 +08:00
Alex Lam S.L
b5e0e8c203 facilitate fix for #1531 (#1542) 2017-03-03 07:12:24 +08:00
Alex Lam S.L
e5cb9275df v2.8.5 2017-03-03 05:14:21 +08:00
Alex Lam S.L
17b81350d4 fix chained assignment with unused (#1540)
When #1450 optimises `a=b=42`, it stops after the first variable even if both are unused.

fixes #1539
2017-03-03 04:45:20 +08:00
kzc
4d63d4f5b3 collapse_vars should not replace constant in for-in init section (#1538)
fixes #1537
2017-03-03 03:51:15 +08:00
Alex Lam S.L
70d72ad806 properly cover all cases of for-in loop variables (#1536) 2017-03-03 02:39:57 +08:00
Alex Lam S.L
fe9227a41b fix reference marking in for-in loops (#1535)
fixes #1533
2017-03-03 00:56:06 +08:00
Alex Lam S.L
b49e142a26 disable do{...}while(false) optimisation (#1534)
- fails to handle `break` in body

fixes #1532
2017-03-03 00:54:41 +08:00
kzc
ee3b39b909 optimize trivial IIFEs returning constants (#1530) 2017-03-02 15:11:40 +08:00
Alex Lam S.L
9699ffb1af trim unused invocation parameters (#1526) 2017-03-02 11:33:59 +08:00
Alex Lam S.L
fdc9b9413b minor improvement to string optimisation (#1514)
- "" + "a"     => "a"
- "" + a + "b" => a + "b"
- "a" + ""     => "a" (improving on #45)
2017-03-02 11:31:39 +08:00
24 changed files with 1415 additions and 158 deletions

View File

@@ -75,8 +75,6 @@ The available options are:
--support-ie8 Use this flag to support Internet Explorer 6/7/8. --support-ie8 Use this flag to support Internet Explorer 6/7/8.
Equivalent to setting `screw_ie8: false` in `minify()` Equivalent to setting `screw_ie8: false` in `minify()`
for `compress`, `mangle` and `output` options. for `compress`, `mangle` and `output` options.
Note: `--support-ie8` may generate incorrect code
for `try`/`catch` in ES5 compliant browsers.
--expr Parse a single expression, rather than a --expr Parse a single expression, rather than a
program (for parsing JSON) program (for parsing JSON)
-p, --prefix Skip prefix for original filenames that appear -p, --prefix Skip prefix for original filenames that appear
@@ -350,6 +348,9 @@ to set `true`; it's effectively a shortcut for `foo=true`).
comparison are switching. Compression only works if both `comparisons` and comparison are switching. Compression only works if both `comparisons` and
`unsafe_comps` are both set to true. `unsafe_comps` are both set to true.
- `unsafe_math` (default: false) -- optimize numerical expressions like
`2 * x * 3` into `6 * x`, which may give imprecise floating point results.
- `unsafe_proto` (default: false) -- optimize expressions like - `unsafe_proto` (default: false) -- optimize expressions like
`Array.prototype.slice.call(a)` into `[].slice.call(a)` `Array.prototype.slice.call(a)` into `[].slice.call(a)`
@@ -423,6 +424,9 @@ to set `true`; it's effectively a shortcut for `foo=true`).
such as `console.info` and/or retain side effects from function arguments such as `console.info` and/or retain side effects from function arguments
after dropping the function call then use `pure_funcs` instead. after dropping the function call then use `pure_funcs` instead.
- `expression` -- default `false`. Pass `true` to preserve completion values
from terminal statements without `return`, e.g. in bookmarklets.
- `keep_fargs` -- default `true`. Prevents the - `keep_fargs` -- default `true`. Prevents the
compressor from discarding unused function arguments. You need this compressor from discarding unused function arguments. You need this
for code which relies on `Function.length`. for code which relies on `Function.length`.

View File

@@ -8,7 +8,6 @@ var sys = require("util");
var yargs = require("yargs"); var yargs = require("yargs");
var fs = require("fs"); var fs = require("fs");
var path = require("path"); var path = require("path");
var async = require("async");
var acorn; var acorn;
var screw_ie8 = true; var screw_ie8 = true;
var ARGS = yargs var ARGS = yargs
@@ -27,7 +26,7 @@ mangling you need to use `-c` and `-m`.\
.describe("source-map-include-sources", "Pass this flag if you want to include the content of source files in the source map as sourcesContent property.") .describe("source-map-include-sources", "Pass this flag if you want to include the content of source files in the source map as sourcesContent property.")
.describe("in-source-map", "Input source map, useful if you're compressing JS that was generated from some other original code.") .describe("in-source-map", "Input source map, useful if you're compressing JS that was generated from some other original code.")
.describe("screw-ie8", "Do not support Internet Explorer 6/7/8. This flag is enabled by default.") .describe("screw-ie8", "Do not support Internet Explorer 6/7/8. This flag is enabled by default.")
.describe("support-ie8", "Support non-standard Internet Explorer 6/7/8 javascript. Note: may generate incorrect code for try/catch in ES5 compliant browsers.") .describe("support-ie8", "Support non-standard Internet Explorer 6/7/8 javascript.")
.describe("expr", "Parse a single expression, rather than a program (for parsing JSON)") .describe("expr", "Parse a single expression, rather than a program (for parsing JSON)")
.describe("p", "Skip prefix for original filenames that appear in source maps. \ .describe("p", "Skip prefix for original filenames that appear in source maps. \
For example -p 3 will drop 3 directories from file names and ensure they are relative paths. \ For example -p 3 will drop 3 directories from file names and ensure they are relative paths. \
@@ -319,8 +318,11 @@ var STATS = {};
var TOPLEVEL = null; var TOPLEVEL = null;
var P_RELATIVE = ARGS.p && ARGS.p == "relative"; var P_RELATIVE = ARGS.p && ARGS.p == "relative";
var SOURCES_CONTENT = {}; var SOURCES_CONTENT = {};
var index = 0;
async.eachLimit(files, 1, function (file, cb) { !function cb() {
if (index == files.length) return done();
var file = files[index++];
read_whole_file(file, function (err, code) { read_whole_file(file, function (err, code) {
if (err) { if (err) {
print_error("ERROR: can't read file: " + file); print_error("ERROR: can't read file: " + file);
@@ -388,7 +390,9 @@ async.eachLimit(files, 1, function (file, cb) {
}); });
cb(); cb();
}); });
}, function () { }();
function done() {
var OUTPUT_FILE = ARGS.o; var OUTPUT_FILE = ARGS.o;
var SOURCE_MAP = (ARGS.source_map || ARGS.source_map_inline) ? UglifyJS.SourceMap({ var SOURCE_MAP = (ARGS.source_map || ARGS.source_map_inline) ? UglifyJS.SourceMap({
@@ -537,7 +541,7 @@ async.eachLimit(files, 1, function (file, cb) {
})); }));
} }
} }
}); }
/* -----[ functions ]----- */ /* -----[ functions ]----- */

View File

@@ -812,9 +812,6 @@ var AST_SymbolAccessor = DEFNODE("SymbolAccessor", null, {
var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", "init", { var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", "init", {
$documentation: "A declaration symbol (symbol in var/const, function name or argument, symbol in catch)", $documentation: "A declaration symbol (symbol in var/const, function name or argument, symbol in catch)",
$propdoc: {
init: "[AST_Node*/S] array of initializers for this declaration."
}
}, AST_Symbol); }, AST_Symbol);
var AST_SymbolVar = DEFNODE("SymbolVar", null, { var AST_SymbolVar = DEFNODE("SymbolVar", null, {

View File

@@ -54,6 +54,7 @@ function Compressor(options, false_by_default) {
drop_debugger : !false_by_default, drop_debugger : !false_by_default,
unsafe : false, unsafe : false,
unsafe_comps : false, unsafe_comps : false,
unsafe_math : false,
unsafe_proto : false, unsafe_proto : false,
conditionals : !false_by_default, conditionals : !false_by_default,
comparisons : !false_by_default, comparisons : !false_by_default,
@@ -79,6 +80,7 @@ function Compressor(options, false_by_default) {
screw_ie8 : true, screw_ie8 : true,
drop_console : false, drop_console : false,
angular : false, angular : false,
expression : false,
warnings : true, warnings : true,
global_defs : {}, global_defs : {},
passes : 1, passes : 1,
@@ -115,12 +117,18 @@ Compressor.prototype = new TreeTransformer;
merge(Compressor.prototype, { merge(Compressor.prototype, {
option: function(key) { return this.options[key] }, option: function(key) { return this.options[key] },
compress: function(node) { compress: function(node) {
if (this.option("expression")) {
node = node.process_expression(true);
}
var passes = +this.options.passes || 1; var passes = +this.options.passes || 1;
for (var pass = 0; pass < passes && pass < 3; ++pass) { for (var pass = 0; pass < passes && pass < 3; ++pass) {
if (pass > 0 || this.option("reduce_vars")) if (pass > 0 || this.option("reduce_vars"))
node.reset_opt_flags(this, true); node.reset_opt_flags(this, true);
node = node.transform(this); node = node.transform(this);
} }
if (this.option("expression")) {
node = node.process_expression(false);
}
return node; return node;
}, },
warn: function(text, props) { warn: function(text, props) {
@@ -177,11 +185,55 @@ merge(Compressor.prototype, {
return this.print_to_string() == node.print_to_string(); return this.print_to_string() == node.print_to_string();
}); });
AST_Node.DEFMETHOD("process_expression", function(insert) {
var self = this;
var tt = new TreeTransformer(function(node) {
if (insert && node instanceof AST_SimpleStatement) {
return make_node(AST_Return, node, {
value: node.body
});
}
if (!insert && node instanceof AST_Return) {
return make_node(AST_SimpleStatement, node, {
body: node.value || make_node(AST_Undefined, node)
});
}
if (node instanceof AST_Lambda && node !== self) {
return node;
}
if (node instanceof AST_Block) {
var index = node.body.length - 1;
if (index >= 0) {
node.body[index] = node.body[index].transform(tt);
}
}
if (node instanceof AST_If) {
node.body = node.body.transform(tt);
if (node.alternative) {
node.alternative = node.alternative.transform(tt);
}
}
if (node instanceof AST_With) {
node.body = node.body.transform(tt);
}
return node;
});
return self.transform(tt);
});
AST_Node.DEFMETHOD("reset_opt_flags", function(compressor, rescan){ AST_Node.DEFMETHOD("reset_opt_flags", function(compressor, rescan){
var reduce_vars = rescan && compressor.option("reduce_vars"); var reduce_vars = rescan && compressor.option("reduce_vars");
var ie8 = !compressor.option("screw_ie8");
var safe_ids = []; var safe_ids = [];
push(); push();
var tw = new TreeWalker(function(node){ var suppressor = new TreeWalker(function(node) {
if (node instanceof AST_Symbol) {
var d = node.definition();
if (node instanceof AST_SymbolRef) d.references.push(node);
d.fixed = false;
}
});
var tw = new TreeWalker(function(node, descend){
if (!(node instanceof AST_Directive || node instanceof AST_Constant)) { if (!(node instanceof AST_Directive || node instanceof AST_Constant)) {
node._squeezed = false; node._squeezed = false;
node._optimized = false; node._optimized = false;
@@ -196,6 +248,9 @@ merge(Compressor.prototype, {
d.fixed = false; d.fixed = false;
} }
} }
if (ie8 && node instanceof AST_SymbolCatch) {
node.definition().fixed = false;
}
if (node instanceof AST_VarDef) { if (node instanceof AST_VarDef) {
var d = node.name.definition(); var d = node.name.definition();
if (d.fixed === undefined) { if (d.fixed === undefined) {
@@ -243,15 +298,19 @@ merge(Compressor.prototype, {
return true; return true;
} }
if (node instanceof AST_ForIn) { if (node instanceof AST_ForIn) {
if (node.init instanceof AST_SymbolRef) { node.init.walk(suppressor);
node.init.definition().fixed = false;
}
node.object.walk(tw); node.object.walk(tw);
push(); push();
node.body.walk(tw); node.body.walk(tw);
pop(); pop();
return true; return true;
} }
if (node instanceof AST_Catch) {
push();
descend();
pop();
return true;
}
} }
}); });
this.walk(tw); this.walk(tw);
@@ -476,8 +535,10 @@ merge(Compressor.prototype, {
// Constant single use vars can be replaced in any scope. // Constant single use vars can be replaced in any scope.
if (var_decl.value.is_constant()) { if (var_decl.value.is_constant()) {
var ctt = new TreeTransformer(function(node) { var ctt = new TreeTransformer(function(node) {
if (node === ref) if (node === ref
&& !ctt.find_parent(AST_ForIn)) {
return replace_var(node, ctt.parent(), true); return replace_var(node, ctt.parent(), true);
}
}); });
stat.transform(ctt); stat.transform(ctt);
continue; continue;
@@ -546,10 +607,7 @@ merge(Compressor.prototype, {
return statements; return statements;
function is_lvalue(node, parent) { function is_lvalue(node, parent) {
return node instanceof AST_SymbolRef && ( return node instanceof AST_SymbolRef && isLHS(node, parent);
(parent instanceof AST_Assign && node === parent.left)
|| (parent instanceof AST_Unary && parent.expression === node
&& (parent.operator == "++" || parent.operator == "--")));
} }
function replace_var(node, parent, is_constant) { function replace_var(node, parent, is_constant) {
if (is_lvalue(node, parent)) return node; if (is_lvalue(node, parent)) return node;
@@ -566,7 +624,7 @@ merge(Compressor.prototype, {
// Further optimize statement after substitution. // Further optimize statement after substitution.
stat.reset_opt_flags(compressor); stat.reset_opt_flags(compressor);
compressor.warn("Replacing " + (is_constant ? "constant" : "variable") + compressor.warn("Collapsing " + (is_constant ? "constant" : "variable") +
" " + var_name + " [{file}:{line},{col}]", node.start); " " + var_name + " [{file}:{line},{col}]", node.start);
CHANGED = true; CHANGED = true;
return value; return value;
@@ -714,7 +772,7 @@ merge(Compressor.prototype, {
CHANGED = true; CHANGED = true;
stat = stat.clone(); stat = stat.clone();
stat.alternative = ret[0] || make_node(AST_Return, stat, { stat.alternative = ret[0] || make_node(AST_Return, stat, {
value: make_node(AST_Undefined, stat) value: null
}); });
ret[0] = stat.transform(compressor); ret[0] = stat.transform(compressor);
continue loop; continue loop;
@@ -747,7 +805,7 @@ merge(Compressor.prototype, {
&& !stat.alternative) { && !stat.alternative) {
CHANGED = true; CHANGED = true;
ret.push(make_node(AST_Return, ret[0], { ret.push(make_node(AST_Return, ret[0], {
value: make_node(AST_Undefined, ret[0]) value: null
}).transform(compressor)); }).transform(compressor));
ret.unshift(stat); ret.unshift(stat);
continue loop; continue loop;
@@ -1004,6 +1062,10 @@ merge(Compressor.prototype, {
})); }));
}; };
function is_undefined(node) {
return node instanceof AST_Undefined || node.is_undefined;
}
/* -----[ boolean/negation helpers ]----- */ /* -----[ boolean/negation helpers ]----- */
// methods to determine whether an expression has a boolean result type // methods to determine whether an expression has a boolean result type
@@ -1034,6 +1096,34 @@ merge(Compressor.prototype, {
node.DEFMETHOD("is_boolean", func); node.DEFMETHOD("is_boolean", func);
}); });
// methods to determine if an expression has a numeric result type
(function (def){
def(AST_Node, return_false);
def(AST_Number, return_true);
var unary = makePredicate("+ - ~ ++ --");
def(AST_Unary, function(){
return unary(this.operator);
});
var binary = makePredicate("- * / % & | ^ << >> >>>");
def(AST_Binary, function(compressor){
return binary(this.operator) || this.operator == "+"
&& this.left.is_number(compressor)
&& this.right.is_number(compressor);
});
var assign = makePredicate("-= *= /= %= &= |= ^= <<= >>= >>>=");
def(AST_Assign, function(compressor){
return assign(this.operator) || this.right.is_number(compressor);
});
def(AST_Seq, function(compressor){
return this.cdr.is_number(compressor);
});
def(AST_Conditional, function(compressor){
return this.consequent.is_number(compressor) && this.alternative.is_number(compressor);
});
})(function(node, func){
node.DEFMETHOD("is_number", func);
});
// methods to determine if an expression has a string result type // methods to determine if an expression has a string result type
(function (def){ (function (def){
def(AST_Node, return_false); def(AST_Node, return_false);
@@ -1054,18 +1144,12 @@ merge(Compressor.prototype, {
def(AST_Conditional, function(compressor){ def(AST_Conditional, function(compressor){
return this.consequent.is_string(compressor) && this.alternative.is_string(compressor); return this.consequent.is_string(compressor) && this.alternative.is_string(compressor);
}); });
def(AST_Call, function(compressor){
return compressor.option("unsafe")
&& this.expression instanceof AST_SymbolRef
&& this.expression.name == "String"
&& this.expression.undeclared();
});
})(function(node, func){ })(function(node, func){
node.DEFMETHOD("is_string", func); node.DEFMETHOD("is_string", func);
}); });
function isLHS(node, parent) { function isLHS(node, parent) {
return parent instanceof AST_Unary && (parent.operator === "++" || parent.operator === "--") return parent instanceof AST_Unary && (parent.operator == "++" || parent.operator == "--")
|| parent instanceof AST_Assign && parent.left === node; || parent instanceof AST_Assign && parent.left === node;
} }
@@ -1799,14 +1883,17 @@ merge(Compressor.prototype, {
} }
return node; return node;
} }
if (drop_vars && assign_as_unused if (drop_vars && assign_as_unused) {
&& node instanceof AST_Assign var n = node;
&& node.operator == "=" while (n instanceof AST_Assign
&& node.left instanceof AST_SymbolRef) { && n.operator == "="
var def = node.left.definition(); && n.left instanceof AST_SymbolRef) {
if (!(def.id in in_use_ids) && self.variables.get(def.name) === def) { var def = n.left.definition();
return node.right; if (def.id in in_use_ids
|| self.variables.get(def.name) !== def) break;
n = n.right;
} }
if (n !== node) return n;
} }
if (node instanceof AST_For) { if (node instanceof AST_For) {
descend(node, this); descend(node, this);
@@ -1995,7 +2082,14 @@ merge(Compressor.prototype, {
def(AST_Constant, return_null); def(AST_Constant, return_null);
def(AST_This, return_null); def(AST_This, return_null);
def(AST_Call, function(compressor, first_in_statement){ def(AST_Call, function(compressor, first_in_statement){
if (!this.has_pure_annotation(compressor) && compressor.pure_funcs(this)) return this; if (!this.has_pure_annotation(compressor) && compressor.pure_funcs(this)) {
if (this.expression instanceof AST_Function) {
var node = this.clone();
node.expression = node.expression.process_expression(false);
return node;
}
return this;
}
if (this.pure) { if (this.pure) {
compressor.warn("Dropping __PURE__ call [{file}:{line},{col}]", this.start); compressor.warn("Dropping __PURE__ call [{file}:{line},{col}]", this.start);
this.pure.value = this.pure.value.replace(/[@#]__PURE__/g, ' '); this.pure.value = this.pure.value.replace(/[@#]__PURE__/g, ' ');
@@ -2138,7 +2232,7 @@ merge(Compressor.prototype, {
} }
} else { } else {
// self instanceof AST_Do // self instanceof AST_Do
return self.body; return self;
} }
} }
if (self instanceof AST_While) { if (self instanceof AST_While) {
@@ -2319,8 +2413,8 @@ merge(Compressor.prototype, {
return make_node(self.body.CTOR, self, { return make_node(self.body.CTOR, self, {
value: make_node(AST_Conditional, self, { value: make_node(AST_Conditional, self, {
condition : self.condition, condition : self.condition,
consequent : self.body.value || make_node(AST_Undefined, self.body).optimize(compressor), consequent : self.body.value || make_node(AST_Undefined, self.body),
alternative : self.alternative.value || make_node(AST_Undefined, self.alternative).optimize(compressor) alternative : self.alternative.value || make_node(AST_Undefined, self.alternative)
}) })
}).transform(compressor); }).transform(compressor);
} }
@@ -2487,8 +2581,22 @@ merge(Compressor.prototype, {
}); });
OPT(AST_Call, function(self, compressor){ OPT(AST_Call, function(self, compressor){
if (compressor.option("unsafe")) {
var exp = self.expression; var exp = self.expression;
if (compressor.option("unused")
&& exp instanceof AST_Function
&& !exp.uses_arguments
&& !exp.uses_eval
&& self.args.length > exp.argnames.length) {
var end = exp.argnames.length;
for (var i = end, len = self.args.length; i < len; i++) {
var node = self.args[i].drop_side_effect_free(compressor);
if (node) {
self.args[end++] = node;
}
}
self.args.length = end;
}
if (compressor.option("unsafe")) {
if (exp instanceof AST_SymbolRef && exp.undeclared()) { if (exp instanceof AST_SymbolRef && exp.undeclared()) {
switch (exp.name) { switch (exp.name) {
case "Array": case "Array":
@@ -2662,16 +2770,24 @@ merge(Compressor.prototype, {
return best_of(self, node); return best_of(self, node);
} }
} }
if (exp instanceof AST_Function) {
if (exp.body[0] instanceof AST_Return) {
var value = exp.body[0].value;
if (!value || value.is_constant()) {
var args = self.args.concat(value || make_node(AST_Undefined, self));
return AST_Seq.from_array(args).transform(compressor);
}
}
if (compressor.option("side_effects")) { if (compressor.option("side_effects")) {
if (self.expression instanceof AST_Function if (!AST_Block.prototype.has_side_effects.call(exp, compressor)) {
&& self.args.length == 0 var args = self.args.concat(make_node(AST_Undefined, self));
&& !AST_Block.prototype.has_side_effects.call(self.expression, compressor)) { return AST_Seq.from_array(args).transform(compressor);
return make_node(AST_Undefined, self).transform(compressor); }
} }
} }
if (compressor.option("drop_console")) { if (compressor.option("drop_console")) {
if (self.expression instanceof AST_PropAccess) { if (exp instanceof AST_PropAccess) {
var name = self.expression.expression; var name = exp.expression;
while (name.expression) { while (name.expression) {
name = name.expression; name = name.expression;
} }
@@ -2713,23 +2829,41 @@ merge(Compressor.prototype, {
self.car = self.car.drop_side_effect_free(compressor, first_in_statement(compressor)); self.car = self.car.drop_side_effect_free(compressor, first_in_statement(compressor));
if (!self.car) return maintain_this_binding(compressor.parent(), self, self.cdr); if (!self.car) return maintain_this_binding(compressor.parent(), self, self.cdr);
if (compressor.option("cascade")) { if (compressor.option("cascade")) {
var left;
if (self.car instanceof AST_Assign if (self.car instanceof AST_Assign
&& !self.car.left.has_side_effects(compressor)) { && !self.car.left.has_side_effects(compressor)) {
if (self.car.left.equivalent_to(self.cdr)) { left = self.car.left;
return self.car; } else if (self.car instanceof AST_Unary
&& (self.car.operator == "++" || self.car.operator == "--")) {
left = self.car.expression;
} }
if (self.cdr instanceof AST_Call if (left) {
&& self.cdr.expression.equivalent_to(self.car.left)) { var parent, field;
self.cdr.expression = self.car; var cdr = self.cdr;
while (true) {
if (cdr.equivalent_to(left)) {
var car = self.car instanceof AST_UnaryPostfix ? make_node(AST_UnaryPrefix, self.car, {
operator: self.car.operator,
expression: left
}) : self.car;
if (parent) {
parent[field] = car;
return self.cdr; return self.cdr;
} }
return car;
} }
if (!self.car.has_side_effects(compressor) if (cdr instanceof AST_Binary && !(cdr instanceof AST_Assign)) {
&& self.car.equivalent_to(self.cdr)) { field = cdr.left.is_constant() ? "right" : "left";
return self.car; } else if (cdr instanceof AST_Call
|| cdr instanceof AST_Unary && cdr.operator != "++" && cdr.operator != "--") {
field = "expression";
} else break;
parent = cdr;
cdr = cdr[field];
} }
} }
if (self.cdr instanceof AST_Undefined) { }
if (is_undefined(self.cdr)) {
return make_node(AST_UnaryPrefix, self, { return make_node(AST_UnaryPrefix, self, {
operator : "void", operator : "void",
expression : self.car expression : self.car
@@ -2768,7 +2902,7 @@ merge(Compressor.prototype, {
self.expression = e; self.expression = e;
return self; return self;
} else { } else {
return make_node(AST_Undefined, self); return make_node(AST_Undefined, self).transform(compressor);
} }
} }
if (compressor.option("booleans") && compressor.in_boolean_context()) { if (compressor.option("booleans") && compressor.in_boolean_context()) {
@@ -2841,8 +2975,14 @@ merge(Compressor.prototype, {
right: rhs[0] right: rhs[0]
}).optimize(compressor); }).optimize(compressor);
} }
function reverse(op, force) { function reversible() {
if (force || !(self.left.has_side_effects(compressor) || self.right.has_side_effects(compressor))) { return self.left instanceof AST_Constant
|| self.right instanceof AST_Constant
|| !self.left.has_side_effects(compressor)
&& !self.right.has_side_effects(compressor);
}
function reverse(op) {
if (reversible()) {
if (op) self.operator = op; if (op) self.operator = op;
var tmp = self.left; var tmp = self.left;
self.left = self.right; self.left = self.right;
@@ -2858,7 +2998,7 @@ merge(Compressor.prototype, {
if (!(self.left instanceof AST_Binary if (!(self.left instanceof AST_Binary
&& PRECEDENCE[self.left.operator] >= PRECEDENCE[self.operator])) { && PRECEDENCE[self.left.operator] >= PRECEDENCE[self.operator])) {
reverse(null, true); reverse();
} }
} }
if (/^[!=]==?$/.test(self.operator)) { if (/^[!=]==?$/.test(self.operator)) {
@@ -2893,6 +3033,7 @@ merge(Compressor.prototype, {
case "===": case "===":
case "!==": case "!==":
if ((self.left.is_string(compressor) && self.right.is_string(compressor)) || if ((self.left.is_string(compressor) && self.right.is_string(compressor)) ||
(self.left.is_number(compressor) && self.right.is_number(compressor)) ||
(self.left.is_boolean() && self.right.is_boolean())) { (self.left.is_boolean() && self.right.is_boolean())) {
self.operator = self.operator.substr(0, 2); self.operator = self.operator.substr(0, 2);
} }
@@ -2985,11 +3126,26 @@ merge(Compressor.prototype, {
} }
} }
} }
if (self.operator == "+" && self.right instanceof AST_String if (self.operator == "+") {
&& self.right.getValue() === "" && self.left instanceof AST_Binary if (self.right instanceof AST_String
&& self.left.operator == "+" && self.left.is_string(compressor)) { && self.right.getValue() == ""
&& self.left.is_string(compressor)) {
return self.left; return self.left;
} }
if (self.left instanceof AST_String
&& self.left.getValue() == ""
&& self.right.is_string(compressor)) {
return self.right;
}
if (self.left instanceof AST_Binary
&& self.left.operator == "+"
&& self.left.left instanceof AST_String
&& self.left.left.getValue() == ""
&& self.right.is_string(compressor)) {
self.left = self.left.right;
return self.transform(compressor);
}
}
if (compressor.option("evaluate")) { if (compressor.option("evaluate")) {
switch (self.operator) { switch (self.operator) {
case "&&": case "&&":
@@ -3015,7 +3171,10 @@ merge(Compressor.prototype, {
} }
break; break;
} }
if (self.operator == "+") { var associative = true;
switch (self.operator) {
case "+":
// "foo" + ("bar" + x) => "foobar" + x
if (self.left instanceof AST_Constant if (self.left instanceof AST_Constant
&& self.right instanceof AST_Binary && self.right instanceof AST_Binary
&& self.right.operator == "+" && self.right.operator == "+"
@@ -3023,7 +3182,7 @@ merge(Compressor.prototype, {
&& self.right.is_string(compressor)) { && self.right.is_string(compressor)) {
self = make_node(AST_Binary, self, { self = make_node(AST_Binary, self, {
operator: "+", operator: "+",
left: make_node(AST_String, null, { left: make_node(AST_String, self.left, {
value: "" + self.left.getValue() + self.right.left.getValue(), value: "" + self.left.getValue() + self.right.left.getValue(),
start: self.left.start, start: self.left.start,
end: self.right.left.end end: self.right.left.end
@@ -3031,6 +3190,7 @@ merge(Compressor.prototype, {
right: self.right.right right: self.right.right
}); });
} }
// (x + "foo") + "bar" => x + "foobar"
if (self.right instanceof AST_Constant if (self.right instanceof AST_Constant
&& self.left instanceof AST_Binary && self.left instanceof AST_Binary
&& self.left.operator == "+" && self.left.operator == "+"
@@ -3039,13 +3199,14 @@ merge(Compressor.prototype, {
self = make_node(AST_Binary, self, { self = make_node(AST_Binary, self, {
operator: "+", operator: "+",
left: self.left.left, left: self.left.left,
right: make_node(AST_String, null, { right: make_node(AST_String, self.right, {
value: "" + self.left.right.getValue() + self.right.getValue(), value: "" + self.left.right.getValue() + self.right.getValue(),
start: self.left.right.start, start: self.left.right.start,
end: self.right.end end: self.right.end
}) })
}); });
} }
// (x + "foo") + ("bar" + y) => (x + "foobar") + y
if (self.left instanceof AST_Binary if (self.left instanceof AST_Binary
&& self.left.operator == "+" && self.left.operator == "+"
&& self.left.is_string(compressor) && self.left.is_string(compressor)
@@ -3059,7 +3220,7 @@ merge(Compressor.prototype, {
left: make_node(AST_Binary, self.left, { left: make_node(AST_Binary, self.left, {
operator: "+", operator: "+",
left: self.left.left, left: self.left.left,
right: make_node(AST_String, null, { right: make_node(AST_String, self.left.right, {
value: "" + self.left.right.getValue() + self.right.left.getValue(), value: "" + self.left.right.getValue() + self.right.left.getValue(),
start: self.left.right.start, start: self.left.right.start,
end: self.right.left.end end: self.right.left.end
@@ -3068,6 +3229,122 @@ merge(Compressor.prototype, {
right: self.right.right right: self.right.right
}); });
} }
// a + -b => a - b
if (self.right instanceof AST_UnaryPrefix
&& self.right.operator == "-"
&& self.left.is_number(compressor)) {
self = make_node(AST_Binary, self, {
operator: "-",
left: self.left,
right: self.right.expression
});
}
// -a + b => b - a
if (self.left instanceof AST_UnaryPrefix
&& self.left.operator == "-"
&& reversible()
&& self.right.is_number(compressor)) {
self = make_node(AST_Binary, self, {
operator: "-",
left: self.right,
right: self.left.expression
});
}
case "*":
associative = compressor.option("unsafe_math");
case "&":
case "|":
case "^":
// a + +b => +b + a
if (self.left.is_number(compressor)
&& self.right.is_number(compressor)
&& reversible()
&& !(self.left instanceof AST_Binary
&& self.left.operator != self.operator
&& PRECEDENCE[self.left.operator] >= PRECEDENCE[self.operator])) {
var reversed = make_node(AST_Binary, self, {
operator: self.operator,
left: self.right,
right: self.left
});
if (self.right instanceof AST_Constant
&& !(self.left instanceof AST_Constant)) {
self = best_of(reversed, self);
} else {
self = best_of(self, reversed);
}
}
if (associative && self.is_number(compressor)) {
// a + (b + c) => (a + b) + c
if (self.right instanceof AST_Binary
&& self.right.operator == self.operator) {
self = make_node(AST_Binary, self, {
operator: self.operator,
left: make_node(AST_Binary, self.left, {
operator: self.operator,
left: self.left,
right: self.right.left,
start: self.left.start,
end: self.right.left.end
}),
right: self.right.right
});
}
// (n + 2) + 3 => 5 + n
// (2 * n) * 3 => 6 + n
if (self.right instanceof AST_Constant
&& self.left instanceof AST_Binary
&& self.left.operator == self.operator) {
if (self.left.left instanceof AST_Constant) {
self = make_node(AST_Binary, self, {
operator: self.operator,
left: make_node(AST_Binary, self.left, {
operator: self.operator,
left: self.left.left,
right: self.right,
start: self.left.left.start,
end: self.right.end
}),
right: self.left.right
});
} else if (self.left.right instanceof AST_Constant) {
self = make_node(AST_Binary, self, {
operator: self.operator,
left: make_node(AST_Binary, self.left, {
operator: self.operator,
left: self.left.right,
right: self.right,
start: self.left.right.start,
end: self.right.end
}),
right: self.left.left
});
}
}
// (a | 1) | (2 | d) => (3 | a) | b
if (self.left instanceof AST_Binary
&& self.left.operator == self.operator
&& self.left.right instanceof AST_Constant
&& self.right instanceof AST_Binary
&& self.right.operator == self.operator
&& self.right.left instanceof AST_Constant) {
self = make_node(AST_Binary, self, {
operator: self.operator,
left: make_node(AST_Binary, self.left, {
operator: self.operator,
left: make_node(AST_Binary, self.left.left, {
operator: self.operator,
left: self.left.right,
right: self.right.left,
start: self.left.right.start,
end: self.right.left.end
}),
right: self.left.left
}),
right: self.right.right
});
}
}
} }
} }
// x && (y && z) ==> x && y && z // x && (y && z) ==> x && y && z
@@ -3100,11 +3377,13 @@ merge(Compressor.prototype, {
return def; return def;
} }
// testing against !self.scope.uses_with first is an optimization // testing against !self.scope.uses_with first is an optimization
if (self.undeclared() && !isLHS(self, compressor.parent()) if (compressor.option("screw_ie8")
&& self.undeclared()
&& !isLHS(self, compressor.parent())
&& (!self.scope.uses_with || !compressor.find_parent(AST_With))) { && (!self.scope.uses_with || !compressor.find_parent(AST_With))) {
switch (self.name) { switch (self.name) {
case "undefined": case "undefined":
return make_node(AST_Undefined, self); return make_node(AST_Undefined, self).transform(compressor);
case "NaN": case "NaN":
return make_node(AST_NaN, self).transform(compressor); return make_node(AST_NaN, self).transform(compressor);
case "Infinity": case "Infinity":
@@ -3147,11 +3426,13 @@ merge(Compressor.prototype, {
var scope = compressor.find_parent(AST_Scope); var scope = compressor.find_parent(AST_Scope);
var undef = scope.find_variable("undefined"); var undef = scope.find_variable("undefined");
if (undef) { if (undef) {
return make_node(AST_SymbolRef, self, { var ref = make_node(AST_SymbolRef, self, {
name : "undefined", name : "undefined",
scope : scope, scope : scope,
thedef : undef thedef : undef
}); });
ref.is_undefined = true;
return ref;
} }
} }
return self; return self;
@@ -3438,7 +3719,7 @@ merge(Compressor.prototype, {
OPT(AST_RegExp, literals_in_boolean_context); OPT(AST_RegExp, literals_in_boolean_context);
OPT(AST_Return, function(self, compressor){ OPT(AST_Return, function(self, compressor){
if (self.value instanceof AST_Undefined) { if (self.value && is_undefined(self.value)) {
self.value = null; self.value = null;
} }
return self; return self;

View File

@@ -46,17 +46,8 @@
var EXPECT_DIRECTIVE = /^$|[;{][\s\n]*$/; var EXPECT_DIRECTIVE = /^$|[;{][\s\n]*$/;
function is_some_comments(comment) { function is_some_comments(comment) {
var text = comment.value;
var type = comment.type;
if (type == "comment2") {
// multiline comment // multiline comment
return /@preserve|@license|@cc_on/i.test(text); return comment.type == "comment2" && /@preserve|@license|@cc_on/i.test(comment.value);
}
return type == "comment5";
}
function is_comment5(comment) {
return comment.type == "comment5";
} }
function OutputStream(options) { function OutputStream(options) {
@@ -86,7 +77,7 @@ function OutputStream(options) {
}, true); }, true);
// Convert comment option to RegExp if neccessary and set up comments filter // Convert comment option to RegExp if neccessary and set up comments filter
var comment_filter = options.shebang ? is_comment5 : return_false; // Default case, throw all comments away except shebangs var comment_filter = return_false; // Default case, throw all comments away
if (options.comments) { if (options.comments) {
var comments = options.comments; var comments = options.comments;
if (typeof options.comments === "string" && /^\/.*\/[a-zA-Z]*$/.test(options.comments)) { if (typeof options.comments === "string" && /^\/.*\/[a-zA-Z]*$/.test(options.comments)) {
@@ -98,12 +89,12 @@ function OutputStream(options) {
} }
if (comments instanceof RegExp) { if (comments instanceof RegExp) {
comment_filter = function(comment) { comment_filter = function(comment) {
return comment.type == "comment5" || comments.test(comment.value); return comment.type != "comment5" && comments.test(comment.value);
}; };
} }
else if (typeof comments === "function") { else if (typeof comments === "function") {
comment_filter = function(comment) { comment_filter = function(comment) {
return comment.type == "comment5" || comments(this, comment); return comment.type != "comment5" && comments(this, comment);
}; };
} }
else if (comments === "some") { else if (comments === "some") {
@@ -400,10 +391,6 @@ function OutputStream(options) {
return OUTPUT; return OUTPUT;
}; };
if (options.preamble) {
print(options.preamble.replace(/\r\n?|[\n\u2028\u2029]|\s*$/g, "\n"));
}
var stack = []; var stack = [];
return { return {
get : get, get : get,
@@ -523,6 +510,17 @@ function OutputStream(options) {
})); }));
} }
if (comments.length > 0 && output.pos() == 0) {
if (output.option("shebang") && comments[0].type == "comment5") {
output.print("#!" + comments.shift().value + "\n");
output.indent();
}
var preamble = output.option("preamble");
if (preamble) {
output.print(preamble.replace(/\r\n?|[\n\u2028\u2029]|\s*$/g, "\n"));
}
}
comments = comments.filter(output.comment_filter, self); comments = comments.filter(output.comment_filter, self);
// Keep single line comments after nlb, after nlb // Keep single line comments after nlb, after nlb
@@ -547,10 +545,6 @@ function OutputStream(options) {
output.space(); output.space();
} }
} }
else if (output.pos() === 0 && c.type == "comment5" && output.option("shebang")) {
output.print("#!" + c.value + "\n");
output.indent();
}
}); });
} }
}); });

View File

@@ -558,6 +558,11 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
function next_token(force_regexp) { function next_token(force_regexp) {
if (force_regexp != null) if (force_regexp != null)
return read_regexp(force_regexp); return read_regexp(force_regexp);
if (shebang && S.pos == 0 && looking_at("#!")) {
start_token();
forward(2);
skip_line_comment("comment5");
}
for (;;) { for (;;) {
skip_whitespace(); skip_whitespace();
start_token(); start_token();
@@ -589,13 +594,6 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
if (PUNC_CHARS(ch)) return token("punc", next()); if (PUNC_CHARS(ch)) return token("punc", next());
if (OPERATOR_CHARS(ch)) return read_operator(); if (OPERATOR_CHARS(ch)) return read_operator();
if (code == 92 || is_identifier_start(code)) return read_word(); if (code == 92 || is_identifier_start(code)) return read_word();
if (shebang) {
if (S.pos == 0 && looking_at("#!")) {
forward(2);
skip_line_comment("comment5");
continue;
}
}
break; break;
} }
parse_error("Unexpected character '" + ch + "'"); parse_error("Unexpected character '" + ch + "'");

View File

@@ -97,7 +97,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
var labels = new Dictionary(); var labels = new Dictionary();
var defun = null; var defun = null;
var tw = new TreeWalker(function(node, descend){ var tw = new TreeWalker(function(node, descend){
if (options.screw_ie8 && node instanceof AST_Catch) { if (node instanceof AST_Catch) {
var save_scope = scope; var save_scope = scope;
scope = new AST_Scope(node); scope = new AST_Scope(node);
scope.init_scope_vars(); scope.init_scope_vars();
@@ -154,12 +154,10 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
} }
else if (node instanceof AST_SymbolVar else if (node instanceof AST_SymbolVar
|| node instanceof AST_SymbolConst) { || node instanceof AST_SymbolConst) {
var def = defun.def_variable(node); defun.def_variable(node);
def.init = tw.parent().value;
} }
else if (node instanceof AST_SymbolCatch) { else if (node instanceof AST_SymbolCatch) {
(options.screw_ie8 ? scope : defun) scope.def_variable(node);
.def_variable(node);
} }
else if (node instanceof AST_LabelRef) { else if (node instanceof AST_LabelRef) {
var sym = labels.get(node.name); var sym = labels.get(node.name);
@@ -209,6 +207,23 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
}); });
self.walk(tw); self.walk(tw);
// pass 3: fix up any scoping issue with IE8
if (!options.screw_ie8) {
self.walk(new TreeWalker(function(node, descend) {
if (node instanceof AST_SymbolCatch) {
var name = node.name;
var scope = node.thedef.scope.parent_scope;
var def = scope.find_variable(name) || self.globals.get(name) || scope.def_variable(node);
node.thedef.references.forEach(function(ref) {
ref.thedef = def;
ref.reference(options);
});
node.thedef = def;
return true;
}
}));
}
if (options.cache) { if (options.cache) {
this.cname = options.cache.cname; this.cname = options.cache.cname;
} }

View File

@@ -4,7 +4,7 @@
"homepage": "http://lisperator.net/uglifyjs", "homepage": "http://lisperator.net/uglifyjs",
"author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)", "author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"version": "2.8.4", "version": "2.8.7",
"engines": { "engines": {
"node": ">=0.8.0" "node": ">=0.8.0"
}, },
@@ -29,7 +29,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"async": "~0.2.6",
"source-map": "~0.5.1", "source-map": "~0.5.1",
"uglify-to-browserify": "~1.0.0", "uglify-to-browserify": "~1.0.0",
"yargs": "~3.10.0" "yargs": "~3.10.0"

View File

@@ -1141,7 +1141,7 @@ collapse_vars_constants: {
function f3(x) { function f3(x) {
var b = x.prop; var b = x.prop;
sideeffect1(); sideeffect1();
return b + (function() { return -9; })(); return b + -9;
} }
} }
} }
@@ -1315,3 +1315,17 @@ collapse_vars_regexp: {
})(); })();
} }
} }
issue_1537: {
options = {
collapse_vars: true,
}
input: {
var k = '';
for (k in {prop: 'val'}){}
}
expect: {
var k = '';
for (k in {prop: 'val'});
}
}

View File

@@ -164,3 +164,53 @@ concat_6: {
); );
} }
} }
concat_7: {
input: {
console.log(
"" + 1,
"" + "1",
"" + 1 + 2,
"" + 1 + "2",
"" + "1" + 2,
"" + "1" + "2",
"" + (x += "foo")
);
}
expect: {
console.log(
"" + 1,
"1",
"" + 1 + 2,
1 + "2",
"1" + 2,
"1" + "2",
x += "foo"
);
}
}
concat_8: {
input: {
console.log(
1 + "",
"1" + "",
1 + 2 + "",
1 + "2" + "",
"1" + 2 + "",
"1" + "2" + "",
(x += "foo") + ""
);
}
expect: {
console.log(
1 + "",
"1",
1 + 2 + "",
1 + "2",
"1" + 2,
"1" + "2",
x += "foo"
);
}
}

View File

@@ -632,7 +632,7 @@ iife: {
} }
expect: { expect: {
function f() { function f() {
~function() {}(b); b;
} }
} }
} }
@@ -679,3 +679,24 @@ const_assign: {
} }
} }
} }
issue_1539: {
options = {
cascade: true,
sequences: true,
side_effects: true,
unused: true,
}
input: {
function f() {
var a, b;
a = b = 42;
return a;
}
}
expect: {
function f() {
return 42;
}
}
}

View File

@@ -640,9 +640,28 @@ call_args: {
expect: { expect: {
const a = 1; const a = 1;
console.log(1); console.log(1);
+(1, 1);
}
}
call_args_drop_param: {
options = {
evaluate: true,
keep_fargs: false,
reduce_vars: true,
unused: true,
}
input: {
const a = 1;
console.log(a);
+function(a) { +function(a) {
return 1; return a;
}(1); }(a, b);
}
expect: {
const a = 1;
console.log(1);
+(b, 1);
} }
} }

View File

@@ -6,3 +6,88 @@ non_ascii_function_identifier_name: {
} }
expect_exact: "function fooλ(δλ){}function λ(δλ){}(function λ(δλ){})();" expect_exact: "function fooλ(δλ){}function λ(δλ){}(function λ(δλ){})();"
} }
iifes_returning_constants_keep_fargs_true: {
options = {
keep_fargs : true,
side_effects : true,
evaluate : true,
unused : true,
dead_code : true,
conditionals : true,
comparisons : true,
booleans : true,
if_return : true,
join_vars : true,
reduce_vars : true,
cascade : true,
}
input: {
(function(){ return -1.23; }());
console.log( function foo(){ return "okay"; }() );
console.log( function foo(x, y, z){ return 123; }() );
console.log( function(x, y, z){ return z; }() );
console.log( function(x, y, z){ if (x) return y; return z; }(1, 2, 3) );
console.log( function(x, y){ return x * y; }(2, 3) );
console.log( function(x, y){ return x * y; }(2, 3, a(), b()) );
}
expect: {
console.log("okay");
console.log(123);
console.log(void 0);
console.log(2);
console.log(6);
console.log((a(), b(), 6));
}
}
iifes_returning_constants_keep_fargs_false: {
options = {
keep_fargs : false,
side_effects : true,
evaluate : true,
unused : true,
dead_code : true,
conditionals : true,
comparisons : true,
booleans : true,
if_return : true,
join_vars : true,
reduce_vars : true,
cascade : true,
}
input: {
(function(){ return -1.23; }());
console.log( function foo(){ return "okay"; }() );
console.log( function foo(x, y, z){ return 123; }() );
console.log( function(x, y, z){ return z; }() );
console.log( function(x, y, z){ if (x) return y; return z; }(1, 2, 3) );
console.log( function(x, y){ return x * y; }(2, 3) );
console.log( function(x, y){ return x * y; }(2, 3, a(), b()) );
}
expect: {
console.log("okay");
console.log(123);
console.log(void 0);
console.log(2);
console.log(6);
console.log((a(), b(), 6));
}
}
issue_485_crashing_1530: {
options = {
conditionals: true,
dead_code: true,
evaluate: true,
}
input: {
(function(a) {
if (true) return;
var b = 42;
})(this);
}
expect: {
this, void 0;
}
}

View File

@@ -2,6 +2,7 @@
unsafe_undefined: { unsafe_undefined: {
options = { options = {
conditionals: true,
if_return: true, if_return: true,
unsafe: true unsafe: true
} }
@@ -19,12 +20,7 @@ unsafe_undefined: {
expect: { expect: {
function f(n) { function f(n) {
return function() { return function() {
if (a) return a ? b : c ? d : n;
return b;
if (c)
return d;
else
return n;
}; };
} }
} }
@@ -32,6 +28,7 @@ unsafe_undefined: {
keep_fnames: { keep_fnames: {
options = { options = {
conditionals: true,
if_return: true, if_return: true,
unsafe: true unsafe: true
} }
@@ -57,12 +54,7 @@ keep_fnames: {
function n(n) { function n(n) {
return n * n; return n * n;
} }
if (a) return a ? b : c ? d : r;
return b;
if (c)
return d;
else
return r;
}; };
} }
} }

View File

@@ -0,0 +1,55 @@
collapse: {
options = {
cascade: true,
sequences: true,
side_effects: true,
unused: true,
}
input: {
function f1() {
var a;
a = typeof b === 'function' ? b() : b;
return a !== undefined && c();
}
function f2(b) {
var a;
b = c();
a = typeof b === 'function' ? b() : b;
return 'stirng' == typeof a && d();
}
function f3(c) {
var a;
a = b(a / 2);
if (a < 0) {
a++;
++c;
return c / 2;
}
}
function f4(c) {
var a;
a = b(a / 2);
if (a < 0) {
a++;
c++;
return c / 2;
}
}
}
expect: {
function f1() {
return void 0 !== ('function' === typeof b ? b() : b) && c();
}
function f2(b) {
return b = c(), 'stirng' == typeof ('function' === typeof b ? b() : b) && d();
}
function f3(c) {
var a;
if ((a = b(a / 2)) < 0) return a++, ++c / 2;
}
function f4(c) {
var a;
if ((a = b(a / 2)) < 0) return a++, ++c / 2;
}
}
}

317
test/compress/issue-640.js Normal file
View File

@@ -0,0 +1,317 @@
cond_5: {
options = {
conditionals: true,
expression: true,
}
input: {
if (some_condition()) {
if (some_other_condition()) {
do_something();
} else {
alternate();
}
} else {
alternate();
}
if (some_condition()) {
if (some_other_condition()) {
do_something();
}
}
}
expect: {
some_condition() && some_other_condition() ? do_something() : alternate();
if (some_condition() && some_other_condition()) do_something();
}
}
dead_code_const_annotation_regex: {
options = {
booleans : true,
conditionals : true,
dead_code : true,
evaluate : true,
expression : true,
loops : true,
}
input: {
var unused;
// @constraint this shouldn't be a constant
var CONST_FOO_ANN = false;
if (CONST_FOO_ANN) {
console.log("reachable");
}
}
expect: {
var unused;
var CONST_FOO_ANN = !1;
if (CONST_FOO_ANN) console.log('reachable');
}
}
drop_console_2: {
options = {
drop_console: true,
expression: true,
}
input: {
console.log('foo');
console.log.apply(console, arguments);
}
expect: {
// with regular compression these will be stripped out as well
void 0;
void 0;
}
}
drop_value: {
options = {
expression: true,
side_effects: true,
}
input: {
(1, [2, foo()], 3, {a:1, b:bar()});
}
expect: {
foo(), {a:1, b:bar()};
}
}
wrongly_optimized: {
options = {
conditionals: true,
booleans: true,
evaluate: true,
expression: true,
}
input: {
function func() {
foo();
}
if (func() || true) {
bar();
}
}
expect: {
function func() {
foo();
}
// TODO: optimize to `func(), bar()`
if (func(), !0) bar();
}
}
negate_iife_1: {
options = {
expression: true,
negate_iife: true,
}
input: {
(function(){ stuff() })();
}
expect: {
(function(){ stuff() })();
}
}
negate_iife_3: {
options = {
conditionals: true,
expression: true,
negate_iife: true,
}
input: {
(function(){ return t })() ? console.log(true) : console.log(false);
}
expect: {
(function(){ return t })() ? console.log(true) : console.log(false);
}
}
negate_iife_3_off: {
options = {
conditionals: true,
expression: true,
negate_iife: false,
}
input: {
(function(){ return t })() ? console.log(true) : console.log(false);
}
expect: {
(function(){ return t })() ? console.log(true) : console.log(false);
}
}
negate_iife_4: {
options = {
conditionals: true,
expression: true,
negate_iife: true,
sequences: true,
}
input: {
(function(){ return t })() ? console.log(true) : console.log(false);
(function(){
console.log("something");
})();
}
expect: {
(function(){ return t })() ? console.log(true) : console.log(false), function(){
console.log("something");
}();
}
}
negate_iife_5: {
options = {
conditionals: true,
expression: true,
negate_iife: true,
sequences: true,
}
input: {
if ((function(){ return t })()) {
foo(true);
} else {
bar(false);
}
(function(){
console.log("something");
})();
}
expect: {
(function(){ return t })() ? foo(true) : bar(false), function(){
console.log("something");
}();
}
}
negate_iife_5_off: {
options = {
conditionals: true,
expression: true,
negate_iife: false,
sequences: true,
};
input: {
if ((function(){ return t })()) {
foo(true);
} else {
bar(false);
}
(function(){
console.log("something");
})();
}
expect: {
(function(){ return t })() ? foo(true) : bar(false), function(){
console.log("something");
}();
}
}
issue_1254_negate_iife_true: {
options = {
expression: true,
negate_iife: true,
}
input: {
(function() {
return function() {
console.log('test')
};
})()();
}
expect_exact: '(function(){return function(){console.log("test")}})()();'
}
issue_1254_negate_iife_nested: {
options = {
expression: true,
negate_iife: true,
}
input: {
(function() {
return function() {
console.log('test')
};
})()()()()();
}
expect_exact: '(function(){return function(){console.log("test")}})()()()()();'
}
conditional: {
options = {
expression: true,
pure_funcs: [ "pure" ],
side_effects: true,
}
input: {
pure(1 | a() ? 2 & b() : 7 ^ c());
pure(1 | a() ? 2 & b() : 5);
pure(1 | a() ? 4 : 7 ^ c());
pure(1 | a() ? 4 : 5);
pure(3 ? 2 & b() : 7 ^ c());
pure(3 ? 2 & b() : 5);
pure(3 ? 4 : 7 ^ c());
pure(3 ? 4 : 5);
}
expect: {
1 | a() ? b() : c();
1 | a() && b();
1 | a() || c();
a();
3 ? b() : c();
3 && b();
3 || c();
pure(3 ? 4 : 5);
}
}
limit_1: {
options = {
expression: true,
sequences: 3,
}
input: {
a;
b;
c;
d;
e;
f;
g;
h;
i;
j;
k;
}
expect: {
// Turned into a single return statement
// so it can no longer be split into lines
a,b,c,d,e,f,g,h,i,j,k;
}
}
iife: {
options = {
expression: true,
sequences: true,
}
input: {
x = 42;
(function a() {})();
!function b() {}();
~function c() {}();
+function d() {}();
-function e() {}();
void function f() {}();
typeof function g() {}();
}
expect: {
x = 42, function a() {}(), function b() {}(), function c() {}(),
function d() {}(), function e() {}(), function f() {}(), typeof function g() {}();
}
}

View File

@@ -213,6 +213,30 @@ evaluate: {
a(); a();
for(;;) for(;;)
c(); c();
d(); // rule disabled due to issue_1532
do d(); while (false);
}
}
issue_1532: {
options = {
evaluate: true,
loops: true,
}
input: {
function f(x, y) {
do {
if (x) break;
foo();
} while (false);
}
}
expect: {
function f(x, y) {
do {
if (x) break;
foo();
} while (false);
}
} }
} }

View File

@@ -32,16 +32,57 @@ negate_iife_2: {
} }
} }
negate_iife_2_side_effects: {
options = {
negate_iife: true,
side_effects: true,
}
input: {
(function(){ return {} })().x = 10; // should not transform this one
}
expect: {
(function(){ return {} })().x = 10;
}
}
negate_iife_3: { negate_iife_3: {
options = { options = {
negate_iife: true, negate_iife: true,
conditionals: true conditionals: true
}; };
input: {
(function(){ return t })() ? console.log(true) : console.log(false);
}
expect: {
!function(){ return t }() ? console.log(false) : console.log(true);
}
}
negate_iife_3_evaluate: {
options = {
conditionals: true,
evaluate: true,
negate_iife: true,
}
input: { input: {
(function(){ return true })() ? console.log(true) : console.log(false); (function(){ return true })() ? console.log(true) : console.log(false);
} }
expect: { expect: {
!function(){ return true }() ? console.log(false) : console.log(true); console.log(true);
}
}
negate_iife_3_side_effects: {
options = {
conditionals: true,
negate_iife: true,
side_effects: true,
}
input: {
(function(){ return t })() ? console.log(true) : console.log(false);
}
expect: {
!function(){ return t }() ? console.log(false) : console.log(true);
} }
} }
@@ -50,11 +91,25 @@ negate_iife_3_off: {
negate_iife: false, negate_iife: false,
conditionals: true, conditionals: true,
}; };
input: {
(function(){ return t })() ? console.log(true) : console.log(false);
}
expect: {
!function(){ return t }() ? console.log(false) : console.log(true);
}
}
negate_iife_3_off_evaluate: {
options = {
conditionals: true,
evaluate: true,
negate_iife: false,
}
input: { input: {
(function(){ return true })() ? console.log(true) : console.log(false); (function(){ return true })() ? console.log(true) : console.log(false);
} }
expect: { expect: {
!function(){ return true }() ? console.log(false) : console.log(true); console.log(true);
} }
} }
@@ -65,13 +120,13 @@ negate_iife_4: {
sequences: true sequences: true
}; };
input: { input: {
(function(){ return true })() ? console.log(true) : console.log(false); (function(){ return t })() ? console.log(true) : console.log(false);
(function(){ (function(){
console.log("something"); console.log("something");
})(); })();
} }
expect: { expect: {
!function(){ return true }() ? console.log(false) : console.log(true), function(){ !function(){ return t }() ? console.log(false) : console.log(true), function(){
console.log("something"); console.log("something");
}(); }();
} }
@@ -86,7 +141,7 @@ sequence_off: {
}; };
input: { input: {
function f() { function f() {
(function(){ return true })() ? console.log(true) : console.log(false); (function(){ return t })() ? console.log(true) : console.log(false);
(function(){ (function(){
console.log("something"); console.log("something");
})(); })();
@@ -95,19 +150,19 @@ sequence_off: {
(function(){ (function(){
console.log("something"); console.log("something");
})(); })();
(function(){ return true })() ? console.log(true) : console.log(false); (function(){ return t })() ? console.log(true) : console.log(false);
} }
} }
expect: { expect: {
function f() { function f() {
!function(){ return true }() ? console.log(false) : console.log(true), function(){ !function(){ return t }() ? console.log(false) : console.log(true), function(){
console.log("something"); console.log("something");
}(); }();
} }
function g() { function g() {
(function(){ (function(){
console.log("something"); console.log("something");
})(), function(){ return true }() ? console.log(true) : console.log(false); })(), function(){ return t }() ? console.log(true) : console.log(false);
} }
} }
} }
@@ -119,7 +174,7 @@ negate_iife_5: {
conditionals: true, conditionals: true,
}; };
input: { input: {
if ((function(){ return true })()) { if ((function(){ return t })()) {
foo(true); foo(true);
} else { } else {
bar(false); bar(false);
@@ -129,7 +184,7 @@ negate_iife_5: {
})(); })();
} }
expect: { expect: {
!function(){ return true }() ? bar(false) : foo(true), function(){ !function(){ return t }() ? bar(false) : foo(true), function(){
console.log("something"); console.log("something");
}(); }();
} }
@@ -142,7 +197,7 @@ negate_iife_5_off: {
conditionals: true, conditionals: true,
}; };
input: { input: {
if ((function(){ return true })()) { if ((function(){ return t })()) {
foo(true); foo(true);
} else { } else {
bar(false); bar(false);
@@ -152,7 +207,7 @@ negate_iife_5_off: {
})(); })();
} }
expect: { expect: {
!function(){ return true }() ? bar(false) : foo(true), function(){ !function(){ return t }() ? bar(false) : foo(true), function(){
console.log("something"); console.log("something");
}(); }();
} }
@@ -320,3 +375,35 @@ issue_1288: {
}(0); }(0);
} }
} }
issue_1288_side_effects: {
options = {
conditionals: true,
negate_iife: true,
side_effects: true,
}
input: {
if (w) ;
else {
(function f() {})();
}
if (!x) {
(function() {
x = {};
})();
}
if (y)
(function() {})();
else
(function(z) {
return z;
})(0);
}
expect: {
w;
x || function() {
x = {};
}();
y;
}
}

View File

@@ -17,3 +17,139 @@ hex_numbers_in_parentheses_for_prototype_functions: {
} }
expect_exact: "-2;(-2).toFixed(0);2;2..toFixed(0);.2;.2.toFixed(0);2e-8;2e-8.toFixed(0);0xde0b6b3a7640080;(0xde0b6b3a7640080).toFixed(0);" expect_exact: "-2;(-2).toFixed(0);2;2..toFixed(0);.2;.2.toFixed(0);2e-8;2e-8.toFixed(0);0xde0b6b3a7640080;(0xde0b6b3a7640080).toFixed(0);"
} }
comparisons: {
options = {
comparisons: true,
}
input: {
console.log(
~x === 42,
x % n === 42
);
}
expect: {
console.log(
42 == ~x,
x % n == 42
);
}
}
evaluate_1: {
options = {
evaluate: true,
unsafe_math: false,
}
input: {
console.log(
x + 1 + 2,
x * 1 * 2,
+x + 1 + 2,
1 + x + 2 + 3,
1 | x | 2 | 3,
1 + x-- + 2 + 3,
1 + (x*y + 2) + 3,
1 + (2 + x + 3),
1 + (2 + ~x + 3),
-y + (2 + ~x + 3),
1 & (2 & x & 3),
1 + (2 + (x |= 0) + 3)
);
}
expect: {
console.log(
x + 1 + 2,
1 * x * 2,
+x + 1 + 2,
1 + x + 2 + 3,
3 | x,
1 + x-- + 2 + 3,
x*y + 2 + 1 + 3,
1 + (2 + x + 3),
2 + ~x + 3 + 1,
-y + (2 + ~x + 3),
0 & x,
2 + (x |= 0) + 3 + 1
);
}
}
evaluate_2: {
options = {
evaluate: true,
unsafe_math: true,
}
input: {
console.log(
x + 1 + 2,
x * 1 * 2,
+x + 1 + 2,
1 + x + 2 + 3,
1 | x | 2 | 3,
1 + x-- + 2 + 3,
1 + (x*y + 2) + 3,
1 + (2 + x + 3),
1 & (2 & x & 3),
1 + (2 + (x |= 0) + 3)
);
}
expect: {
console.log(
x + 1 + 2,
2 * x,
3 + +x,
1 + x + 2 + 3,
3 | x,
6 + x--,
6 + x*y,
1 + (2 + x + 3),
0 & x,
6 + (x |= 0)
);
}
}
evaluate_3: {
options = {
evaluate: true,
unsafe: true,
unsafe_math: true,
}
input: {
console.log(1 + Number(x) + 2);
}
expect: {
console.log(3 + +x);
}
}
evaluate_4: {
options = {
evaluate: true,
}
input: {
console.log(
1+ +a,
+a+1,
1+-a,
-a+1,
+a+ +b,
+a+-b,
-a+ +b,
-a+-b
);
}
expect: {
console.log(
+a+1,
+a+1,
1-a,
1-a,
+a+ +b,
+a-b,
-a+ +b,
-a-b
);
}
}

View File

@@ -563,7 +563,7 @@ inner_var_for: {
} }
} }
inner_var_for_in: { inner_var_for_in_1: {
options = { options = {
evaluate: true, evaluate: true,
reduce_vars: true, reduce_vars: true,
@@ -589,3 +589,77 @@ inner_var_for_in: {
x(1, b, c, d); x(1, b, c, d);
} }
} }
inner_var_for_in_2: {
options = {
evaluate: true,
reduce_vars: true,
}
input: {
for (var long_name in {})
console.log(long_name);
}
expect: {
for (var long_name in {})
console.log(long_name);
}
}
inner_var_catch: {
options = {
evaluate: true,
reduce_vars: true,
}
input: {
try {
a();
} catch (e) {
var b = 1;
}
console.log(b);
}
expect: {
try {
a();
} catch (e) {
var b = 1;
}
console.log(b);
}
}
issue_1533_1: {
options = {
collapse_vars: true,
reduce_vars: true,
}
input: {
var id = "";
for (id in {break: "me"})
console.log(id);
}
expect: {
var id = "";
for (id in {break: "me"})
console.log(id);
}
}
issue_1533_2: {
options = {
evaluate: true,
reduce_vars: true,
}
input: {
var id = "";
for (var id in {break: "me"})
console.log(id);
console.log(id);
}
expect: {
var id = "";
for (var id in {break: "me"})
console.log(id);
console.log(id);
}
}

View File

@@ -17,6 +17,26 @@ dont_screw: {
expect_exact: 'f("\\x0B");'; expect_exact: 'f("\\x0B");';
} }
do_screw_constants: {
options = {
screw_ie8: true,
}
input: {
f(undefined, Infinity);
}
expect_exact: "f(void 0,1/0);"
}
dont_screw_constants: {
options = {
screw_ie8: false,
}
input: {
f(undefined, Infinity);
}
expect_exact: "f(undefined,Infinity);"
}
do_screw_try_catch: { do_screw_try_catch: {
options = { screw_ie8: true }; options = { screw_ie8: true };
mangle = { screw_ie8: true }; mangle = { screw_ie8: true };
@@ -46,8 +66,6 @@ do_screw_try_catch: {
} }
dont_screw_try_catch: { dont_screw_try_catch: {
// This test is known to generate incorrect code for screw_ie8=false.
// Update expected result in the event this bug is ever fixed.
options = { screw_ie8: false }; options = { screw_ie8: false };
mangle = { screw_ie8: false }; mangle = { screw_ie8: false };
beautify = { screw_ie8: false }; beautify = { screw_ie8: false };
@@ -64,11 +82,11 @@ dont_screw_try_catch: {
} }
expect: { expect: {
bad = function(n){ bad = function(n){
return function(n){ return function(t){
try{ try{
t() n()
} catch(t) { } catch(n) {
n(t) t(n)
} }
} }
}; };
@@ -104,8 +122,6 @@ do_screw_try_catch_undefined: {
} }
dont_screw_try_catch_undefined: { dont_screw_try_catch_undefined: {
// This test is known to generate incorrect code for screw_ie8=false.
// Update expected result in the event this bug is ever fixed.
options = { screw_ie8: false }; options = { screw_ie8: false };
mangle = { screw_ie8: false }; mangle = { screw_ie8: false };
beautify = { screw_ie8: false }; beautify = { screw_ie8: false };
@@ -121,14 +137,48 @@ dont_screw_try_catch_undefined: {
}; };
} }
expect: { expect: {
function a(o){ function a(n){
try{ try{
throw "Stuff" throw "Stuff"
} catch (n) { } catch (undefined) {
console.log("caught: "+n) console.log("caught: " + undefined)
} }
console.log("undefined is " + n); console.log("undefined is " + undefined);
return o === n return n === undefined
}
}
}
reduce_vars: {
options = {
evaluate: true,
reduce_vars: true,
screw_ie8: false,
unused: true,
}
mangle = {
screw_ie8: false,
}
input: {
function f() {
var a;
try {
x();
} catch (a) {
y();
}
alert(a);
}
}
expect: {
function f() {
var t;
try {
x();
} catch (t) {
y();
}
alert(t);
} }
} }
} }

View File

@@ -248,6 +248,39 @@ iife: {
} }
expect: { expect: {
x = 42, function a() {}(), function b() {}(), function c() {}(), x = 42, function a() {}(), function b() {}(), function c() {}(),
function d() {}(), function e() {}(), function f() {}(), function g() {}() function d() {}(), function e() {}(), function f() {}(), function g() {}();
}
}
unsafe_undefined: {
options = {
conditionals: true,
if_return: true,
sequences: true,
side_effects: true,
unsafe: true,
}
input: {
function f(undefined) {
if (a)
return b;
if (c)
return d;
}
function g(undefined) {
if (a)
return b;
if (c)
return d;
e();
}
}
expect: {
function f(undefined) {
return a ? b : c ? d : undefined;
}
function g(undefined) {
return a ? b : c ? d : void e();
}
} }
} }

View File

@@ -72,4 +72,12 @@ describe("comment filters", function() {
assert.strictEqual(UglifyJS.parse("/* ok */ function a(){}").print_to_string(options), "/* ok */function a(){}"); assert.strictEqual(UglifyJS.parse("/* ok */ function a(){}").print_to_string(options), "/* ok */function a(){}");
assert.strictEqual(UglifyJS.parse("/* ok */ function a(){}").print_to_string(options), "/* ok */function a(){}"); assert.strictEqual(UglifyJS.parse("/* ok */ function a(){}").print_to_string(options), "/* ok */function a(){}");
}); });
it("Should handle shebang and preamble correctly", function() {
var code = UglifyJS.minify("#!/usr/bin/node\nvar x = 10;", {
fromString: true,
output: { preamble: "/* Build */" }
}).code;
assert.strictEqual(code, "#!/usr/bin/node\n/* Build */\nvar x=10;");
})
}); });

View File

@@ -155,7 +155,7 @@ describe("minify", function() {
assert.strictEqual(code, "// comment1 comment2\nbar();"); assert.strictEqual(code, "// comment1 comment2\nbar();");
}); });
it("should not drop #__PURE__ hint if function is retained", function() { it("should not drop #__PURE__ hint if function is retained", function() {
var result = Uglify.minify("var a = /*#__PURE__*/(function(){return 1})();", { var result = Uglify.minify("var a = /*#__PURE__*/(function(){ foo(); })();", {
fromString: true, fromString: true,
output: { output: {
comments: "all", comments: "all",
@@ -163,7 +163,7 @@ describe("minify", function() {
} }
}); });
var code = result.code; var code = result.code;
assert.strictEqual(code, "var a=/*#__PURE__*/function(){return 1}();"); assert.strictEqual(code, "var a=/*#__PURE__*/function(){foo()}();");
}) })
}); });