Merge remote-tracking branch 'mishoo/master'

This commit is contained in:
Jo Simard 2012-10-29 14:33:38 -04:00
commit a049a7afe5
13 changed files with 423 additions and 48 deletions

View File

@ -3,8 +3,11 @@ UglifyJS 2
UglifyJS is a JavaScript parser, minifier, compressor or beautifier toolkit.
For now this page documents the command line utility. More advanced
API documentation will be made available later.
This page documents the command line utility. For
[API and internals documentation see my website](http://lisperator.net/uglifyjs/).
There's also an
[in-browser online demo](http://lisperator.net/uglifyjs/#demo) (for Firefox,
Chrome and probably Safari).
Install
-------
@ -319,6 +322,7 @@ There's a single toplevel function which combines all the steps. If you
don't need additional customization, you might want to go with `minify`.
Example:
// see "fromString" below if you need to pass code instead of file name
var result = UglifyJS.minify("/path/to/file.js");
console.log(result.code); // minified output
@ -339,6 +343,14 @@ Note that the source map is not saved in a file, it's just returned in
`result.map`. The value passed for `outSourceMap` is only used to set the
`file` attribute in the source map (see [the spec][sm-spec]).
You can also specify sourceRoot property to be included in source map:
var result = UglifyJS.minify([ "file1.js", "file2.js", "file3.js" ], {
outSourceMap: "out.js.map",
sourceRoot: "http://example.com/src"
});
If you're compressing compiled JavaScript and have a source map for it, you
can use the `inSourceMap` argument:
@ -351,6 +363,12 @@ can use the `inSourceMap` argument:
The `inSourceMap` is only used if you also request `outSourceMap` (it makes
no sense otherwise).
Other options:
- `warnings` (default `false`) — pass `true` to display compressor warnings.
- `fromString` (default `false`) — if you pass `true` then you can pass
JavaScript source code, rather than file names.
We could add more options to `UglifyJS.minify` — if you need additional
functionality please suggest!

View File

@ -127,7 +127,7 @@ if (ARGS.comments) {
var type = comment.type;
if (type == "comment2") {
// multiline comment
return /@preserve|@license|@cc_on/i.test(test);
return /@preserve|@license|@cc_on/i.test(text);
}
}
}

View File

@ -581,6 +581,18 @@ var AST_Seq = DEFNODE("Seq", "car cdr", {
}
return list;
},
to_array: function() {
var p = this, a = [];
while (p) {
a.push(p.car);
if (p.cdr && !(p.cdr instanceof AST_Seq)) {
a.push(p.cdr);
break;
}
p = p.cdr;
}
return a;
},
add: function(node) {
var p = this;
while (p) {
@ -929,10 +941,10 @@ TreeWalker.prototype = {
} else {
for (var i = stack.length; --i >= 0;) {
var x = stack[i];
if (x instanceof AST_Switch) return x;
if (x instanceof AST_For || x instanceof AST_ForIn || x instanceof AST_DWLoop) {
return (x.body instanceof AST_BlockStatement ? x.body : x);
}
if (x instanceof AST_Switch
|| x instanceof AST_For
|| x instanceof AST_ForIn
|| x instanceof AST_DWLoop) return x;
}
}
}

View File

@ -162,6 +162,9 @@ merge(Compressor.prototype, {
if (val === null) {
return make_node(AST_Null, orig).optimize(compressor);
}
if (val instanceof RegExp) {
return make_node(AST_RegExp, orig).optimize(compressor);
}
throw new Error(string_template("Can't handle constant of type: {type}", {
type: typeof val
}));
@ -183,6 +186,14 @@ merge(Compressor.prototype, {
return false;
};
function loop_body(x) {
if (x instanceof AST_Switch) return x;
if (x instanceof AST_For || x instanceof AST_ForIn || x instanceof AST_DWLoop) {
return (x.body instanceof AST_BlockStatement ? x.body : x);
}
return x;
};
function tighten_body(statements, compressor) {
var CHANGED;
do {
@ -300,8 +311,13 @@ merge(Compressor.prototype, {
}
var ab = aborts(stat.body);
var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab.label) : null;
if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda)
|| (ab instanceof AST_Continue && self === compressor.loopcontrol_target(ab.label)))) {
|| (ab instanceof AST_Continue && self === loop_body(lct))
|| (ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct))) {
if (ab.label) {
remove(ab.label.thedef.references, ab.label);
}
CHANGED = true;
var body = as_statement_array(stat.body).slice(0, -1);
stat = stat.clone();
@ -317,8 +333,13 @@ merge(Compressor.prototype, {
}
var ab = aborts(stat.alternative);
var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab.label) : null;
if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda)
|| (ab instanceof AST_Continue && self === compressor.loopcontrol_target(ab.label)))) {
|| (ab instanceof AST_Continue && self === loop_body(lct))
|| (ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct))) {
if (ab.label) {
remove(ab.label.thedef.references, ab.label);
}
CHANGED = true;
stat = stat.clone();
stat.body = make_node(AST_BlockStatement, stat.body, {
@ -344,14 +365,27 @@ merge(Compressor.prototype, {
function eliminate_dead_code(statements, compressor) {
var has_quit = false;
var orig = statements.length;
var self = compressor.self();
statements = statements.reduce(function(a, stat){
if (has_quit) {
extract_declarations_from_unreachable_code(compressor, stat, a);
} else {
a.push(stat);
if (stat instanceof AST_Jump) {
has_quit = true;
if (stat instanceof AST_LoopControl) {
var lct = compressor.loopcontrol_target(stat.label);
if ((stat instanceof AST_Break
&& lct instanceof AST_BlockStatement
&& loop_body(lct) === self) || (stat instanceof AST_Continue
&& loop_body(lct) === self)) {
if (stat.label) {
remove(stat.label.thedef.references, stat.label);
}
} else {
a.push(stat);
}
} else {
a.push(stat);
}
if (aborts(stat)) has_quit = true;
}
return a;
}, []);
@ -707,9 +741,10 @@ merge(Compressor.prototype, {
});
def(AST_SimpleStatement, function(){
if (this.body instanceof AST_Function) return false;
return this.body.has_side_effects();
});
def(AST_Defun, function(){ return true });
def(AST_Function, function(){ return false });
def(AST_Binary, function(){
return this.left.has_side_effects()
|| this.right.has_side_effects();
@ -771,6 +806,9 @@ merge(Compressor.prototype, {
var n = this.body.length;
return n > 0 && aborts(this.body[n - 1]);
});
def(AST_If, function(){
return this.alternative && aborts(this.body) && aborts(this.alternative);
});
})(function(node, func){
node.DEFMETHOD("aborts", func);
});
@ -791,6 +829,10 @@ merge(Compressor.prototype, {
});
OPT(AST_LabeledStatement, function(self, compressor){
if (self.body instanceof AST_Break
&& compressor.loopcontrol_target(self.body.label) === self.body) {
return make_node(AST_EmptyStatement, self);
}
return self.label.references.length == 0 ? self.body : self;
});
@ -1108,7 +1150,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 });
return make_node(AST_BlockStatement, self, { body: a }).transform(compressor);
}
} else {
compressor.warn("Condition always false [{file}:{line},{col}]", self.condition.start);
@ -1116,7 +1158,7 @@ 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 });
return make_node(AST_BlockStatement, self, { body: a }).transform(compressor);
}
}
}
@ -1133,7 +1175,7 @@ merge(Compressor.prototype, {
if (is_empty(self.body) && is_empty(self.alternative)) {
return make_node(AST_SimpleStatement, self.condition, {
body: self.condition
});
}).transform(compressor);
}
if (self.body instanceof AST_SimpleStatement
&& self.alternative instanceof AST_SimpleStatement) {
@ -1143,7 +1185,7 @@ merge(Compressor.prototype, {
consequent : self.body.body,
alternative : self.alternative.body
})
});
}).transform(compressor);
}
if (is_empty(self.alternative) && self.body instanceof AST_SimpleStatement) {
if (negated_is_best) return make_node(AST_SimpleStatement, self, {
@ -1152,14 +1194,14 @@ merge(Compressor.prototype, {
left : negated,
right : self.body.body
})
});
}).transform(compressor);
return make_node(AST_SimpleStatement, self, {
body: make_node(AST_Binary, self, {
operator : "&&",
left : self.condition,
right : self.body.body
})
});
}).transform(compressor);
}
if (self.body instanceof AST_EmptyStatement
&& self.alternative
@ -1170,7 +1212,7 @@ merge(Compressor.prototype, {
left : self.condition,
right : self.alternative.body
})
});
}).transform(compressor);
}
if (self.body instanceof AST_Exit
&& self.alternative instanceof AST_Exit
@ -1178,10 +1220,10 @@ merge(Compressor.prototype, {
return make_node(self.body.CTOR, self, {
value: make_node(AST_Conditional, self, {
condition : self.condition,
consequent : self.body.value,
alternative : self.alternative.value || make_node(AST_Undefined, self).optimize(compressor)
consequent : self.body.value || make_node(AST_Undefined, self.body).optimize(compressor),
alternative : self.alternative.value || make_node(AST_Undefined, self.alternative).optimize(compressor)
})
});
}).transform(compressor);
}
if (self.body instanceof AST_If
&& !self.body.alternative
@ -1199,7 +1241,7 @@ merge(Compressor.prototype, {
self.alternative = null;
return make_node(AST_BlockStatement, self, {
body: [ self, alt ]
});
}).transform(compressor);
}
}
if (aborts(self.alternative)) {
@ -1209,16 +1251,21 @@ merge(Compressor.prototype, {
self.alternative = null;
return make_node(AST_BlockStatement, self, {
body: [ self, body ]
});
}).transform(compressor);
}
return self;
});
OPT(AST_Switch, function(self, compressor){
if (self.body.length == 0 && compressor.option("conditionals")) {
return make_node(AST_SimpleStatement, self, {
body: self.expression
}).transform(compressor);
}
var last_branch = self.body[self.body.length - 1];
if (last_branch) {
var stat = last_branch.body[last_branch.body.length - 1]; // last statement
if (stat instanceof AST_Break && compressor.loopcontrol_target(stat.label) === self)
if (stat instanceof AST_Break && loop_body(compressor.loopcontrol_target(stat.label)) === self)
last_branch.body.pop();
}
return self;
@ -1302,14 +1349,14 @@ merge(Compressor.prototype, {
left: exp.expression,
operator: "+",
right: make_node(AST_String, self, { value: "" })
});
}).transform(compressor);
}
}
if (compressor.option("side_effects")) {
if (self.expression instanceof AST_Function
&& self.args.length == 0
&& !self.expression.has_side_effects()) {
return make_node(AST_Undefined, self);
&& !AST_Block.prototype.has_side_effects.call(self.expression)) {
return make_node(AST_Undefined, self).transform(compressor);
}
}
return self;
@ -1333,6 +1380,10 @@ merge(Compressor.prototype, {
});
OPT(AST_Seq, function(self, compressor){
if (!compressor.option("side_effects"))
return self;
if (!self.car.has_side_effects())
return self.cdr;
if (compressor.option("cascade")) {
if (self.car instanceof AST_Assign
&& !self.car.left.has_side_effects()
@ -1348,7 +1399,26 @@ merge(Compressor.prototype, {
return self;
});
AST_Unary.DEFMETHOD("lift_sequences", function(compressor){
if (compressor.option("sequences")) {
if (this.expression instanceof AST_Seq) {
var seq = this.expression;
var x = seq.to_array();
this.expression = x.pop();
x.push(this);
seq = AST_Seq.from_array(x).transform(compressor);
return seq;
}
}
return this;
});
OPT(AST_UnaryPostfix, function(self, compressor){
return self.lift_sequences(compressor);
});
OPT(AST_UnaryPrefix, function(self, compressor){
self = self.lift_sequences(compressor);
var e = self.expression;
if (compressor.option("booleans") && compressor.in_boolean_context()) {
switch (self.operator) {
@ -1371,7 +1441,32 @@ merge(Compressor.prototype, {
return self.evaluate(compressor)[0];
});
AST_Binary.DEFMETHOD("lift_sequences", function(compressor){
if (compressor.option("sequences")) {
if (this.left instanceof AST_Seq) {
var seq = this.left;
var x = seq.to_array();
this.left = x.pop();
x.push(this);
seq = AST_Seq.from_array(x).transform(compressor);
return seq;
}
if (this.right instanceof AST_Seq
&& !(this.operator == "||" || this.operator == "&&")
&& !this.left.has_side_effects()) {
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;
}
}
return this;
});
OPT(AST_Binary, function(self, compressor){
self = self.lift_sequences(compressor);
if (compressor.option("comparisons")) switch (self.operator) {
case "===":
case "!==":
@ -1510,6 +1605,7 @@ merge(Compressor.prototype, {
var ASSIGN_OPS = [ '+', '-', '/', '*', '%', '>>', '<<', '>>>', '|', '^', '&' ];
OPT(AST_Assign, function(self, compressor){
self = self.lift_sequences(compressor);
if (self.operator == "="
&& self.left instanceof AST_SymbolRef
&& self.right instanceof AST_Binary
@ -1613,4 +1709,14 @@ merge(Compressor.prototype, {
return self;
});
function literals_in_boolean_context(self, compressor) {
if (compressor.option("booleans") && compressor.in_boolean_context()) {
return make_node(AST_True, self);
}
return self;
};
OPT(AST_Array, literals_in_boolean_context);
OPT(AST_Object, literals_in_boolean_context);
OPT(AST_RegExp, literals_in_boolean_context);
})();

View File

@ -414,7 +414,7 @@ function OutputStream(options) {
var p = output.parent();
return p instanceof AST_Call // (foo, bar)() or foo(1, (2, 3), 4)
|| p instanceof AST_Unary // !(foo, bar, baz)
|| p instanceof AST_Binary // 1 + (2, 3) + 4 ==> 7
|| p instanceof AST_Binary // 1 + (2, 3) + 4 ==> 8
|| p instanceof AST_VarDef // var a = (1, 2), b = a + a; ==> b == 4
|| p instanceof AST_Dot // (1, {foo:2}).foo ==> 2
|| p instanceof AST_Array // [ 1, (2, 3), 4 ] ==> [ 1, 3, 4 ]

View File

@ -115,7 +115,14 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
node.init_scope_vars();
}
if (node instanceof AST_SymbolLambda) {
scope.def_function(node);
//scope.def_function(node);
//
// https://github.com/mishoo/UglifyJS2/issues/24 — MSIE
// leaks function expression names into the containing
// scope. Don't like this fix but seems we can't do any
// better. IE: please die. Please!
(node.scope = scope.parent_scope).def_function(node);
node.init.push(tw.parent());
}
else if (node instanceof AST_SymbolDefun) {

View File

@ -166,6 +166,12 @@ function string_template(text, props) {
});
};
function remove(array, el) {
for (var i = array.length; --i >= 0;) {
if (array[i] === el) array.splice(i, 1);
}
};
function mergeSort(array, cmp) {
if (array.length < 2) return array.slice();
function merge(a, b) {

View File

@ -3,7 +3,7 @@
"description": "JavaScript parser, mangler/compressor and beautifier toolkit",
"homepage": "http://lisperator.net/uglifyjs",
"main": "tools/node.js",
"version": "2.0.0",
"version": "2.1.4",
"engines": { "node" : ">=0.4.0" },
"maintainers": [{
"name": "Mihai Bazon",

17
test/compress/issue-22.js Normal file
View File

@ -0,0 +1,17 @@
return_with_no_value_in_if_body: {
options = { conditionals: true };
input: {
function foo(bar) {
if (bar) {
return;
} else {
return 1;
}
}
}
expect: {
function foo (bar) {
return bar ? void 0 : 1;
}
}
}

163
test/compress/labels.js Normal file
View File

@ -0,0 +1,163 @@
labels_1: {
options = { if_return: true, conditionals: true, dead_code: true };
input: {
out: {
if (foo) break out;
console.log("bar");
}
};
expect: {
foo || console.log("bar");
}
}
labels_2: {
options = { if_return: true, conditionals: true, dead_code: true };
input: {
out: {
if (foo) print("stuff");
else break out;
console.log("here");
}
};
expect: {
if (foo) {
print("stuff");
console.log("here");
}
}
}
labels_3: {
options = { if_return: true, conditionals: true, dead_code: true };
input: {
for (var i = 0; i < 5; ++i) {
if (i < 3) continue;
console.log(i);
}
};
expect: {
for (var i = 0; i < 5; ++i)
i < 3 || console.log(i);
}
}
labels_4: {
options = { if_return: true, conditionals: true, dead_code: true };
input: {
out: for (var i = 0; i < 5; ++i) {
if (i < 3) continue out;
console.log(i);
}
};
expect: {
for (var i = 0; i < 5; ++i)
i < 3 || console.log(i);
}
}
labels_5: {
options = { if_return: true, conditionals: true, dead_code: true };
// should keep the break-s in the following
input: {
while (foo) {
if (bar) break;
console.log("foo");
}
out: while (foo) {
if (bar) break out;
console.log("foo");
}
};
expect: {
while (foo) {
if (bar) break;
console.log("foo");
}
out: while (foo) {
if (bar) break out;
console.log("foo");
}
}
}
labels_6: {
input: {
out: break out;
};
expect: {}
}
labels_7: {
options = { if_return: true, conditionals: true, dead_code: true };
input: {
while (foo) {
x();
y();
continue;
}
};
expect: {
while (foo) {
x();
y();
}
}
}
labels_8: {
options = { if_return: true, conditionals: true, dead_code: true };
input: {
while (foo) {
x();
y();
break;
}
};
expect: {
while (foo) {
x();
y();
break;
}
}
}
labels_9: {
options = { if_return: true, conditionals: true, dead_code: true };
input: {
out: while (foo) {
x();
y();
continue out;
z();
k();
}
};
expect: {
while (foo) {
x();
y();
}
}
}
labels_10: {
options = { if_return: true, conditionals: true, dead_code: true };
input: {
out: while (foo) {
x();
y();
break out;
z();
k();
}
};
expect: {
out: while (foo) {
x();
y();
break out;
}
}
}

View File

@ -87,3 +87,43 @@ make_sequences_4: {
with (x = 5, obj);
}
}
lift_sequences_1: {
options = { sequences: true };
input: {
foo = !(x(), y(), bar());
}
expect: {
x(), y(), foo = !bar();
}
}
lift_sequences_2: {
options = { sequences: true, evaluate: true };
input: {
q = 1 + (foo(), bar(), 5) + 7 * (5 / (3 - (a(), (QW=ER), c(), 2))) - (x(), y(), 5);
}
expect: {
foo(), bar(), a(), QW = ER, c(), x(), y(), q = 36
}
}
lift_sequences_3: {
options = { sequences: true, conditionals: true };
input: {
x = (foo(), bar(), baz()) ? 10 : 20;
}
expect: {
foo(), bar(), x = baz() ? 10 : 20;
}
}
lift_sequences_4: {
options = { side_effects: true };
input: {
x = (foo, bar, baz);
}
expect: {
x = baz;
}
}

View File

@ -73,12 +73,13 @@ function run_compress_tests() {
var cmp = new U.Compressor(options, true);
var expect = make_code(as_toplevel(test.expect), false);
var input = as_toplevel(test.input);
var input_code = make_code(test.input);
var output = input.transform(cmp);
output.figure_out_scope();
output = make_code(output, false);
if (expect != output) {
log("!!! failed\n---INPUT---\n{input}\n---OUTPUT---\n{output}\n---EXPECTED---\n{expected}\n\n", {
input: make_code(test.input),
input: input_code,
output: output,
expected: expect
});

View File

@ -1,27 +1,27 @@
var save_stderr = process.stderr;
var path = require("path");
var fs = require("fs");
// discard annoying NodeJS warning ("path.existsSync is now called `fs.existsSync`.")
var devnull = fs.createWriteStream("/dev/null");
process.__defineGetter__("stderr", function(){
return devnull;
});
// Avoid NodeJS warning.
//
// There's a --no-deprecation command line argument supported by
// NodeJS, but that's tricky to use, so I'd like to set it from the
// program itself. Turns out you need to set `process.noDeprecation`,
// but by the time you can set that the `path` module is already
// loaded and `path.existsSync` is already changed to display that
// warning, therefore here's the poor solution:
if (fs.existsSync) {
path.existsSync = fs.existsSync;
}
var vm = require("vm");
var sys = require("util");
var path = require("path");
var UglifyJS = vm.createContext({
sys : sys,
console : console,
MOZ_SourceMap : require("source-map")
});
process.__defineGetter__("stderr", function(){
return save_stderr;
});
function load_global(file) {
file = path.resolve(path.dirname(module.filename), file);
try {
@ -66,7 +66,9 @@ exports.minify = function(files, options)
{
options = UglifyJS.defaults(options, {
outSourceMap : null,
sourceRoot : null,
inSourceMap : null,
fromString : false,
warnings : false,
compressor : null,
output : null,
@ -104,9 +106,11 @@ exports.minify = function(files, options)
// 1. parse
var toplevel = null;
files.forEach(function(file){
var code = fs.readFileSync(file, "utf8");
var code = options.fromString
? file
: fs.readFileSync(file, "utf8");
toplevel = UglifyJS.parse(code, {
filename: file,
filename: options.fromString ? "?" : file,
toplevel: toplevel
});
});
@ -133,7 +137,8 @@ exports.minify = function(files, options)
}
if (options.outSourceMap) map = UglifyJS.SourceMap({
file: options.outSourceMap,
orig: inMap
orig: inMap,
root: options.sourceRoot
});
// Add sourcemap to output options