Compare commits

..

44 Commits

Author SHA1 Message Date
Mihai Bazon
64270b9778 v2.1.10 2012-11-08 12:33:27 +02:00
Mihai Bazon
e312c5c2a7 fix API breakage
close #36, #38
2012-11-08 12:31:28 +02:00
Mihai Bazon
1a5fd3e052 optimization for if/break as first statement in a loop body
for(...; x; ...) if (y) break; → for(...; x&&!y; ...);

similarly for `while` and some combinations (i.e. the `break` appears in the
`else` clause, etc.)
2012-11-08 11:43:14 +02:00
Mihai Bazon
5a7e54cf72 ignore node_modules/ 2012-11-07 15:27:12 +02:00
Mihai Bazon
39f8a62703 v2.1.9 2012-11-07 13:31:58 +02:00
Mihai Bazon
46be3f2bf1 fix another small regression
we do need parens here: `new (foo.bar().baz)`, but not here: `new foo.bar.baz`
2012-11-07 13:31:43 +02:00
Mihai Bazon
258b46f4dc v2.1.8 2012-11-07 13:03:11 +02:00
Mihai Bazon
80da21dab4 fix regression from 5346fb94 (shouldn't parenthesize i++ in x[i++]) 2012-11-07 13:02:51 +02:00
Mihai Bazon
bb0e4d7126 v2.1.7 2012-11-07 12:45:23 +02:00
Mihai Bazon
5276a4a873 add AST_Accessor and AST_SymbolAccessor node types
AST_Accessor will represent the function for a setter or getter.  Since they
are not mangleable, and they should not introduce a name in scope, we have a
new node for their name (AST_SymbolAccessor) which doesn't inherit from
AST_SymbolDeclaration.

fix #37
2012-11-07 12:43:27 +02:00
Mihai Bazon
a1ae0c8609 parenthesize property access when it's the expression in New
refs #35
2012-11-07 12:26:33 +02:00
Mihai Bazon
a90c1aeafe further fix for parens around New (refs #35) 2012-11-07 11:49:06 +02:00
Mihai Bazon
ff388a8d2d parenthesize a Call expression when its parent is New
fix #35
2012-11-07 11:36:15 +02:00
Mihai Bazon
5346fb94bb add proper parens around unary expressions
fix #34
2012-11-07 11:23:50 +02:00
Mihai Bazon
a4f6d46118 add option to mangle names even if eval/with is in use
(for more fair comparison to Closure compiler)
2012-11-06 18:19:51 +02:00
Mihai Bazon
7f5f4d60b7 discard the hack that worked around the deprecation warning
(since the source-map module no longer uses require.js)

refs #9
2012-11-05 22:23:51 +02:00
Mihai Bazon
ffccb233e5 convert while into for 2012-11-05 16:01:20 +02:00
Mihai Bazon
fba0c1aafe minor 2012-11-05 16:01:09 +02:00
Mihai Bazon
774f2ded94 minor optimization
for `==` or `!=` against a constant, prefer to display the constant first.
should help a bit after gzip, i.e.:

    typeof foo=="undefined"
    ^^^^^^    ^^^^^^^^^^^^^

vs:

    "undefined"==typeof foo
    ^^^^^^^^^^^^^^^^^^^     (longer sequence that could repeat)

idea stolen from closure.
2012-11-05 13:13:06 +02:00
Mihai Bazon
85af942d64 print final semicolon
close #28
2012-11-05 13:09:39 +02:00
Mihai Bazon
8413787efc use a Dictionary object instead of plain object for hashes
to mitigate the `__proto__` issue

related to #30
2012-11-02 10:58:45 +02:00
Mihai Bazon
dde57452aa v2.1.6 2012-11-01 16:55:10 +02:00
Mihai Bazon
cf409800be it's safe to negate expression in !EXP only in boolean context
#kendo
2012-11-01 15:49:05 +02:00
Mihai Bazon
18270dd9f3 added unsafe_comps for negating <= with >
since it has the potential to break code, let's keep it disabled by default
2012-11-01 15:14:56 +02:00
Mihai Bazon
d4c25c571b fix compressing UnaryPrefix
only try negating the expression if the operator is `!`

#kendo
2012-11-01 13:35:08 +02:00
Mihai Bazon
5248b79506 v2.1.5 2012-10-30 14:51:05 +02:00
Mihai Bazon
abe0ebbf02 don't move expressions containing the binary in operator into the for initializer
(opera can't parse it)

close #25
2012-10-30 14:50:47 +02:00
Mihai Bazon
0852f5595e v2.1.4 2012-10-25 18:52:49 +03:00
Mihai Bazon
cb3cafa14d cripple scope to make IE happy :-(
close #24
2012-10-25 18:52:35 +03:00
Mihai Bazon
202fb93799 test for fs.existsSync 2012-10-25 10:58:48 +03:00
Mihai Bazon
7b87d2ef83 v2.1.3 2012-10-24 09:41:40 +03:00
Mihai Bazon
70fd2b1f33 fix for if (...) return; else return ...;
(it was assumed that the first `return` always contains a value)

close #22
2012-10-24 09:33:32 +03:00
Mihai Bazon
30faaf13ed more sequence optimizations (lift some sequences above binary/unary expressions so that we can avoid parens) 2012-10-22 11:58:06 +03:00
Mihai Bazon
41be8632d3 v2.1.2 2012-10-22 07:57:28 +03:00
Mihai Bazon
bee01dc1be Merge branch 'master' of github.com:mishoo/UglifyJS2 2012-10-20 11:14:25 +03:00
Mihai Bazon
12f71e01d0 alternate hack to disable deprecation warning
ref #9, close #20
2012-10-20 11:12:21 +03:00
Mihai Bazon
3a72deacab Merge pull request #19 from SevInf/master
Allow to specify sourceRoot in minify
2012-10-19 04:29:40 -07:00
Mihai Bazon
fc8314e810 minor fix for dropping unused definitions.
function f(x, y) {
        var g = function() { return h() };
        var h = function() { return g() };
        return x + y;
    }

now compresses to `function f(x, y) { return x + y }`
2012-10-19 12:57:29 +03:00
Sergej Tatarincev
11dffe950e Add sourceRoot option to minify 2012-10-19 12:35:19 +03:00
Mihai Bazon
6f45928a73 add fromString argument to UglifyJS.minify (allows to pass the source
code, instead of file names, as first argument).

close #17
2012-10-18 15:49:15 +03:00
Mihai Bazon
afb7faa6fa more optimizations for some break/continue cases 2012-10-18 15:14:57 +03:00
Mihai Bazon
6aa56f92fe v2.1.1 2012-10-18 10:54:30 +03:00
Mihai Bazon
4fe4257c69 fix --comments (close #16) 2012-10-18 10:54:10 +03:00
Mihai Bazon
a5e75c5a21 v2.1.0 2012-10-17 22:00:11 +03:00
16 changed files with 771 additions and 120 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
tmp/ tmp/
node_modules/

View File

@@ -322,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`. don't need additional customization, you might want to go with `minify`.
Example: Example:
// see "fromString" below if you need to pass code instead of file name
var result = UglifyJS.minify("/path/to/file.js"); var result = UglifyJS.minify("/path/to/file.js");
console.log(result.code); // minified output console.log(result.code); // minified output
@@ -342,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 `result.map`. The value passed for `outSourceMap` is only used to set the
`file` attribute in the source map (see [the spec][sm-spec]). `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 If you're compressing compiled JavaScript and have a source map for it, you
can use the `inSourceMap` argument: can use the `inSourceMap` argument:
@@ -354,6 +363,12 @@ can use the `inSourceMap` argument:
The `inSourceMap` is only used if you also request `outSourceMap` (it makes The `inSourceMap` is only used if you also request `outSourceMap` (it makes
no sense otherwise). 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 We could add more options to `UglifyJS.minify` — if you need additional
functionality please suggest! functionality please suggest!

View File

@@ -127,7 +127,7 @@ if (ARGS.comments) {
var type = comment.type; var type = comment.type;
if (type == "comment2") { if (type == "comment2") {
// multiline comment // multiline comment
return /@preserve|@license|@cc_on/i.test(test); return /@preserve|@license|@cc_on/i.test(text);
} }
} }
} }
@@ -252,7 +252,7 @@ if (SCOPE_IS_NEEDED) {
time_it("scope", function(){ time_it("scope", function(){
TOPLEVEL.figure_out_scope(); TOPLEVEL.figure_out_scope();
if (MANGLE) { if (MANGLE) {
TOPLEVEL.compute_char_frequency(); TOPLEVEL.compute_char_frequency(MANGLE);
} }
}); });
} }

View File

@@ -345,6 +345,10 @@ var AST_Lambda = DEFNODE("Lambda", "name argnames uses_arguments", {
} }
}, AST_Scope); }, AST_Scope);
var AST_Accessor = DEFNODE("Accessor", null, {
$documentation: "A setter/getter function"
}, AST_Lambda);
var AST_Function = DEFNODE("Function", null, { var AST_Function = DEFNODE("Function", null, {
$documentation: "A function expression" $documentation: "A function expression"
}, AST_Lambda); }, AST_Lambda);
@@ -581,6 +585,18 @@ var AST_Seq = DEFNODE("Seq", "car cdr", {
} }
return list; 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) { add: function(node) {
var p = this; var p = this;
while (p) { while (p) {
@@ -746,6 +762,10 @@ var AST_Symbol = DEFNODE("Symbol", "scope name thedef", {
$documentation: "Base class for all symbols", $documentation: "Base class for all symbols",
}); });
var AST_SymbolAccessor = DEFNODE("SymbolAccessor", null, {
$documentation: "The name of a property accessor (setter/getter function)"
}, AST_Symbol);
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: { $propdoc: {
@@ -929,10 +949,10 @@ TreeWalker.prototype = {
} else { } else {
for (var i = stack.length; --i >= 0;) { for (var i = stack.length; --i >= 0;) {
var x = stack[i]; var x = stack[i];
if (x instanceof AST_Switch) return x; if (x instanceof AST_Switch
if (x instanceof AST_For || x instanceof AST_ForIn || x instanceof AST_DWLoop) { || x instanceof AST_For
return (x.body instanceof AST_BlockStatement ? x.body : x); || x instanceof AST_ForIn
} || x instanceof AST_DWLoop) return x;
} }
} }
} }

View File

@@ -53,6 +53,7 @@ function Compressor(options, false_by_default) {
dead_code : !false_by_default, dead_code : !false_by_default,
drop_debugger : !false_by_default, drop_debugger : !false_by_default,
unsafe : !false_by_default, unsafe : !false_by_default,
unsafe_comps : false,
conditionals : !false_by_default, conditionals : !false_by_default,
comparisons : !false_by_default, comparisons : !false_by_default,
evaluate : !false_by_default, evaluate : !false_by_default,
@@ -186,6 +187,14 @@ merge(Compressor.prototype, {
return false; 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) { function tighten_body(statements, compressor) {
var CHANGED; var CHANGED;
do { do {
@@ -303,8 +312,13 @@ merge(Compressor.prototype, {
} }
var ab = aborts(stat.body); 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) 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; CHANGED = true;
var body = as_statement_array(stat.body).slice(0, -1); var body = as_statement_array(stat.body).slice(0, -1);
stat = stat.clone(); stat = stat.clone();
@@ -320,8 +334,13 @@ merge(Compressor.prototype, {
} }
var ab = aborts(stat.alternative); 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) 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; CHANGED = true;
stat = stat.clone(); stat = stat.clone();
stat.body = make_node(AST_BlockStatement, stat.body, { stat.body = make_node(AST_BlockStatement, stat.body, {
@@ -347,11 +366,26 @@ merge(Compressor.prototype, {
function eliminate_dead_code(statements, compressor) { function eliminate_dead_code(statements, compressor) {
var has_quit = false; var has_quit = false;
var orig = statements.length; var orig = statements.length;
var self = compressor.self();
statements = statements.reduce(function(a, stat){ statements = statements.reduce(function(a, stat){
if (has_quit) { if (has_quit) {
extract_declarations_from_unreachable_code(compressor, stat, a); extract_declarations_from_unreachable_code(compressor, stat, a);
} else { } else {
a.push(stat); 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; if (aborts(stat)) has_quit = true;
} }
return a; return a;
@@ -394,12 +428,23 @@ merge(Compressor.prototype, {
var ret = [], prev = null; var ret = [], prev = null;
statements.forEach(function(stat){ statements.forEach(function(stat){
if (prev) { if (prev) {
if (stat instanceof AST_For && stat.init && !(stat.init instanceof AST_Definitions)) { if (stat instanceof AST_For) {
stat.init = cons_seq(stat.init); var opera = {};
} try {
else if (stat instanceof AST_For && !stat.init) { prev.body.walk(new TreeWalker(function(node){
stat.init = prev.body; if (node instanceof AST_Binary && node.operator == "in")
ret.pop(); throw opera;
}));
if (stat.init && !(stat.init instanceof AST_Definitions)) {
stat.init = cons_seq(stat.init);
}
else if (!stat.init) {
stat.init = prev.body;
ret.pop();
}
} catch(ex) {
if (ex !== opera) throw ex;
}
} }
else if (stat instanceof AST_If) { else if (stat instanceof AST_If) {
stat.condition = cons_seq(stat.condition); stat.condition = cons_seq(stat.condition);
@@ -659,7 +704,7 @@ merge(Compressor.prototype, {
}); });
def(AST_Binary, function(compressor){ def(AST_Binary, function(compressor){
var self = this.clone(), op = this.operator; var self = this.clone(), op = this.operator;
if (compressor.option("comparisons") && compressor.option("unsafe")) { if (compressor.option("unsafe_comps")) {
switch (op) { switch (op) {
case "<=" : self.operator = ">" ; return self; case "<=" : self.operator = ">" ; return self;
case "<" : self.operator = ">=" ; return self; case "<" : self.operator = ">=" ; return self;
@@ -708,9 +753,10 @@ merge(Compressor.prototype, {
}); });
def(AST_SimpleStatement, function(){ def(AST_SimpleStatement, function(){
if (this.body instanceof AST_Function) return false;
return this.body.has_side_effects(); return this.body.has_side_effects();
}); });
def(AST_Defun, function(){ return true });
def(AST_Function, function(){ return false });
def(AST_Binary, function(){ def(AST_Binary, function(){
return this.left.has_side_effects() return this.left.has_side_effects()
|| this.right.has_side_effects(); || this.right.has_side_effects();
@@ -795,6 +841,10 @@ merge(Compressor.prototype, {
}); });
OPT(AST_LabeledStatement, function(self, compressor){ 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; return self.label.references.length == 0 ? self.body : self;
}); });
@@ -976,7 +1026,7 @@ merge(Compressor.prototype, {
if (hoist_funs || hoist_vars) { if (hoist_funs || hoist_vars) {
var dirs = []; var dirs = [];
var hoisted = []; var hoisted = [];
var vars = {}, vars_found = 0, var_decl = 0; var vars = new Dictionary(), vars_found = 0, var_decl = 0;
// let's count var_decl first, we seem to waste a lot of // let's count var_decl first, we seem to waste a lot of
// space if we hoist `var` when there's only one. // space if we hoist `var` when there's only one.
self.walk(new TreeWalker(function(node){ self.walk(new TreeWalker(function(node){
@@ -1001,7 +1051,7 @@ merge(Compressor.prototype, {
} }
if (node instanceof AST_Var && hoist_vars) { if (node instanceof AST_Var && hoist_vars) {
node.definitions.forEach(function(def){ node.definitions.forEach(function(def){
vars[def.name.name] = def; vars.set(def.name.name, def);
++vars_found; ++vars_found;
}); });
var seq = node.to_assignments(); var seq = node.to_assignments();
@@ -1025,8 +1075,8 @@ merge(Compressor.prototype, {
); );
self = self.transform(tt); self = self.transform(tt);
if (vars_found > 0) hoisted.unshift(make_node(AST_Var, self, { if (vars_found > 0) hoisted.unshift(make_node(AST_Var, self, {
definitions: Object.keys(vars).map(function(name){ definitions: vars.map(function(def){
var def = vars[name].clone(); def = def.clone();
def.value = null; def.value = null;
return def; return def;
}) })
@@ -1068,6 +1118,61 @@ merge(Compressor.prototype, {
return self; return self;
}); });
function if_break_in_loop(self, compressor) {
function drop_it(rest) {
rest = as_statement_array(rest);
if (self.body instanceof AST_BlockStatement) {
self.body = self.body.clone();
self.body.body = rest.concat(self.body.body.slice(1));
self.body = self.body.transform(compressor);
} else {
self.body = make_node(AST_BlockStatement, self.body, {
body: rest
}).transform(compressor);
}
if_break_in_loop(self, compressor);
}
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) {
if (self.condition) {
self.condition = make_node(AST_Binary, self.condition, {
left: self.condition,
operator: "&&",
right: first.condition.negate(compressor),
});
} else {
self.condition = first.condition.negate(compressor);
}
drop_it(first.alternative);
}
else if (first.alternative instanceof AST_Break
&& compressor.loopcontrol_target(first.alternative.label) === self) {
if (self.condition) {
self.condition = make_node(AST_Binary, self.condition, {
left: self.condition,
operator: "&&",
right: first.condition,
});
} else {
self.condition = first.condition;
}
drop_it(first.body);
}
}
};
OPT(AST_While, function(self, compressor) {
if (!compressor.option("loops")) return self;
self = AST_DWLoop.prototype.optimize.call(self, compressor);
if (self instanceof AST_While) {
if_break_in_loop(self, compressor);
self = make_node(AST_For, self, self).transform(compressor);
}
return self;
});
OPT(AST_For, function(self, compressor){ OPT(AST_For, function(self, compressor){
var cond = self.condition; var cond = self.condition;
if (cond) { if (cond) {
@@ -1092,6 +1197,7 @@ merge(Compressor.prototype, {
} }
} }
} }
if_break_in_loop(self, compressor);
return self; return self;
}); });
@@ -1182,8 +1288,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, consequent : self.body.value || make_node(AST_Undefined, self.body).optimize(compressor),
alternative : self.alternative.value || make_node(AST_Undefined, self).optimize(compressor) alternative : self.alternative.value || make_node(AST_Undefined, self.alternative).optimize(compressor)
}) })
}).transform(compressor); }).transform(compressor);
} }
@@ -1227,7 +1333,7 @@ merge(Compressor.prototype, {
var last_branch = self.body[self.body.length - 1]; var last_branch = self.body[self.body.length - 1];
if (last_branch) { if (last_branch) {
var stat = last_branch.body[last_branch.body.length - 1]; // last statement 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(); last_branch.body.pop();
} }
return self; return self;
@@ -1317,7 +1423,7 @@ merge(Compressor.prototype, {
if (compressor.option("side_effects")) { if (compressor.option("side_effects")) {
if (self.expression instanceof AST_Function if (self.expression instanceof AST_Function
&& self.args.length == 0 && self.args.length == 0
&& !self.expression.has_side_effects()) { && !AST_Block.prototype.has_side_effects.call(self.expression)) {
return make_node(AST_Undefined, self).transform(compressor); return make_node(AST_Undefined, self).transform(compressor);
} }
} }
@@ -1342,6 +1448,10 @@ merge(Compressor.prototype, {
}); });
OPT(AST_Seq, function(self, compressor){ 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 (compressor.option("cascade")) {
if (self.car instanceof AST_Assign if (self.car instanceof AST_Assign
&& !self.car.left.has_side_effects() && !self.car.left.has_side_effects()
@@ -1357,7 +1467,26 @@ merge(Compressor.prototype, {
return self; 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){ OPT(AST_UnaryPrefix, function(self, compressor){
self = self.lift_sequences(compressor);
var e = self.expression; var e = self.expression;
if (compressor.option("booleans") && compressor.in_boolean_context()) { if (compressor.option("booleans") && compressor.in_boolean_context()) {
switch (self.operator) { switch (self.operator) {
@@ -1373,14 +1502,53 @@ merge(Compressor.prototype, {
compressor.warn("Boolean expression always true [{file}:{line},{col}]", self.start); compressor.warn("Boolean expression always true [{file}:{line},{col}]", self.start);
return make_node(AST_True, self); return make_node(AST_True, self);
} }
} if (e instanceof AST_Binary && self.operator == "!") {
if (e instanceof AST_Binary) { self = best_of(self, e.negate(compressor));
self = best_of(self, e.negate(compressor)); }
} }
return self.evaluate(compressor)[0]; 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;
});
var commutativeOperators = makePredicate("== === != !== * & | ^");
OPT(AST_Binary, function(self, compressor){ OPT(AST_Binary, function(self, compressor){
function reverse(op) {
if (op) self.operator = op;
var tmp = self.left;
self.left = self.right;
self.right = tmp;
};
if (commutativeOperators(self.operator)) {
if (self.right instanceof AST_Constant
&& !(self.left instanceof AST_Constant)) {
reverse();
}
}
self = self.lift_sequences(compressor);
if (compressor.option("comparisons")) switch (self.operator) { if (compressor.option("comparisons")) switch (self.operator) {
case "===": case "===":
case "!==": case "!==":
@@ -1391,21 +1559,10 @@ merge(Compressor.prototype, {
// XXX: intentionally falling down to the next case // XXX: intentionally falling down to the next case
case "==": case "==":
case "!=": case "!=":
if (self.left instanceof AST_UnaryPrefix if (self.left instanceof AST_String
&& self.left.operator == "typeof" && self.left.value == "undefined"
&& self.right instanceof AST_String && self.right instanceof AST_UnaryPrefix
&& self.right.value == "undefined") { && self.right.operator == "typeof") {
if (!(self.left.expression instanceof AST_SymbolRef)
|| !self.left.expression.undeclared()) {
self.left = self.left.expression;
self.right = make_node(AST_Undefined, self.right).optimize(compressor);
if (self.operator.length == 2) self.operator += "=";
}
}
else if (self.left instanceof AST_String
&& self.left.value == "undefined"
&& self.right instanceof AST_UnaryPrefix
&& self.right.operator == "typeof") {
if (!(self.right.expression instanceof AST_SymbolRef) if (!(self.right.expression instanceof AST_SymbolRef)
|| !self.right.expression.undeclared()) { || !self.right.expression.undeclared()) {
self.left = self.right.expression; self.left = self.right.expression;
@@ -1468,12 +1625,6 @@ merge(Compressor.prototype, {
}); });
self = best_of(self, negated); self = best_of(self, negated);
} }
var reverse = function(op) {
self.operator = op;
var tmp = self.left;
self.left = self.right;
self.right = tmp;
};
switch (self.operator) { switch (self.operator) {
case "<": reverse(">"); break; case "<": reverse(">"); break;
case "<=": reverse(">="); break; case "<=": reverse(">="); break;
@@ -1519,6 +1670,7 @@ merge(Compressor.prototype, {
var ASSIGN_OPS = [ '+', '-', '/', '*', '%', '>>', '<<', '>>>', '|', '^', '&' ]; var ASSIGN_OPS = [ '+', '-', '/', '*', '%', '>>', '<<', '>>>', '|', '^', '&' ];
OPT(AST_Assign, function(self, compressor){ OPT(AST_Assign, function(self, compressor){
self = self.lift_sequences(compressor);
if (self.operator == "=" if (self.operator == "="
&& self.left instanceof AST_SymbolRef && self.left instanceof AST_SymbolRef
&& self.right instanceof AST_Binary && self.right instanceof AST_Binary

View File

@@ -137,7 +137,7 @@ function OutputStream(options) {
str = String(str); str = String(str);
var ch = str.charAt(0); var ch = str.charAt(0);
if (might_need_semicolon) { if (might_need_semicolon) {
if (";}".indexOf(ch) < 0 && !/[;]$/.test(last)) { if ((!ch || ";}".indexOf(ch) < 0) && !/[;]$/.test(last)) {
if (options.semicolons || requireSemicolonChars(ch)) { if (options.semicolons || requireSemicolonChars(ch)) {
OUTPUT += ";"; OUTPUT += ";";
current_col++; current_col++;
@@ -410,11 +410,16 @@ function OutputStream(options) {
return first_in_statement(output); return first_in_statement(output);
}); });
PARENS(AST_Unary, function(output){
var p = output.parent();
return p instanceof AST_PropAccess && p.expression === this;
});
PARENS(AST_Seq, function(output){ PARENS(AST_Seq, function(output){
var p = output.parent(); var p = output.parent();
return p instanceof AST_Call // (foo, bar)() or foo(1, (2, 3), 4) return p instanceof AST_Call // (foo, bar)() or foo(1, (2, 3), 4)
|| p instanceof AST_Unary // !(foo, bar, baz) || 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_VarDef // var a = (1, 2), b = a + a; ==> b == 4
|| p instanceof AST_Dot // (1, {foo:2}).foo ==> 2 || p instanceof AST_Dot // (1, {foo:2}).foo ==> 2
|| p instanceof AST_Array // [ 1, (2, 3), 4 ] ==> [ 1, 3, 4 ] || p instanceof AST_Array // [ 1, (2, 3), 4 ] ==> [ 1, 3, 4 ]
@@ -463,10 +468,36 @@ function OutputStream(options) {
} }
}); });
PARENS(AST_PropAccess, function(output){
var p = output.parent();
if (p instanceof AST_New && p.expression === this) {
// i.e. new (foo.bar().baz)
//
// if there's one call into this subtree, then we need
// parens around it too, otherwise the call will be
// interpreted as passing the arguments to the upper New
// expression.
try {
this.walk(new TreeWalker(function(node){
if (node instanceof AST_Call) throw p;
}));
} catch(ex) {
if (ex !== p) throw ex;
return true;
}
}
});
PARENS(AST_Call, function(output){
var p = output.parent();
return p instanceof AST_New && p.expression === this;
});
PARENS(AST_New, function(output){ PARENS(AST_New, function(output){
var p = output.parent(); var p = output.parent();
// (new Date).getTime(); if (no_constructor_parens(this, output)
if (p instanceof AST_Dot && no_constructor_parens(this, output)) && (p instanceof AST_Dot // (new Date).getTime()
|| p instanceof AST_Call && p.expression === this)) // (new foo)(bar)
return true; return true;
}); });
@@ -529,6 +560,7 @@ function OutputStream(options) {
}); });
DEFPRINT(AST_Toplevel, function(self, output){ DEFPRINT(AST_Toplevel, function(self, output){
display_body(self.body, true, output); display_body(self.body, true, output);
output.print("");
}); });
DEFPRINT(AST_LabeledStatement, function(self, output){ DEFPRINT(AST_LabeledStatement, function(self, output){
self.label.print(output); self.label.print(output);

View File

@@ -883,6 +883,8 @@ function parse($TEXT, options) {
var function_ = function(in_statement, ctor) { var function_ = function(in_statement, ctor) {
var name = is("name") ? as_symbol(in_statement var name = is("name") ? as_symbol(in_statement
? AST_SymbolDefun ? AST_SymbolDefun
: ctor === AST_Accessor
? AST_SymbolAccessor
: AST_SymbolLambda) : null; : AST_SymbolLambda) : null;
if (in_statement && !name) if (in_statement && !name)
unexpected(); unexpected();
@@ -1158,7 +1160,7 @@ function parse($TEXT, options) {
a.push(new AST_ObjectGetter({ a.push(new AST_ObjectGetter({
start : start, start : start,
key : name, key : name,
value : function_(false, AST_Lambda), value : function_(false, AST_Accessor),
end : prev() end : prev()
})); }));
continue; continue;
@@ -1167,7 +1169,7 @@ function parse($TEXT, options) {
a.push(new AST_ObjectSetter({ a.push(new AST_ObjectSetter({
start : start, start : start,
key : name, key : name,
value : function_(false, AST_Lambda), value : function_(false, AST_Accessor),
end : prev() end : prev()
})); }));
continue; continue;

View File

@@ -43,7 +43,7 @@
"use strict"; "use strict";
function SymbolDef(scope, orig) { function SymbolDef(scope, index, orig) {
this.name = orig.name; this.name = orig.name;
this.orig = [ orig ]; this.orig = [ orig ];
this.scope = scope; this.scope = scope;
@@ -52,15 +52,18 @@ function SymbolDef(scope, orig) {
this.mangled_name = null; this.mangled_name = null;
this.undeclared = false; this.undeclared = false;
this.constant = false; this.constant = false;
this.index = index;
}; };
SymbolDef.prototype = { SymbolDef.prototype = {
unmangleable: function() { unmangleable: function(options) {
return this.global || this.undeclared || this.scope.uses_eval || this.scope.uses_with; return this.global
|| this.undeclared
|| (!(options && options.eval) && (this.scope.uses_eval || this.scope.uses_with));
}, },
mangle: function() { mangle: function(options) {
if (!this.mangled_name && !this.unmangleable()) if (!this.mangled_name && !this.unmangleable(options))
this.mangled_name = this.scope.next_mangled(); this.mangled_name = this.scope.next_mangled(options);
} }
}; };
@@ -75,14 +78,17 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
// pass 1: setup scope chaining and handle definitions // pass 1: setup scope chaining and handle definitions
var self = this; var self = this;
var scope = self.parent_scope = null; var scope = self.parent_scope = null;
var labels = Object.create(null); var labels = new Dictionary();
var nesting = 0;
var tw = new TreeWalker(function(node, descend){ var tw = new TreeWalker(function(node, descend){
if (node instanceof AST_Scope) { if (node instanceof AST_Scope) {
node.init_scope_vars(); node.init_scope_vars(nesting);
var save_scope = node.parent_scope = scope; var save_scope = node.parent_scope = scope;
++nesting;
scope = node; scope = node;
descend(); descend();
scope = save_scope; scope = save_scope;
--nesting;
return true; // don't descend again in TreeWalker return true; // don't descend again in TreeWalker
} }
if (node instanceof AST_Directive) { if (node instanceof AST_Directive) {
@@ -97,11 +103,11 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
} }
if (node instanceof AST_LabeledStatement) { if (node instanceof AST_LabeledStatement) {
var l = node.label; var l = node.label;
if (labels[l.name]) if (labels.has(l.name))
throw new Error(string_template("Label {name} defined twice", l)); throw new Error(string_template("Label {name} defined twice", l));
labels[l.name] = l; labels.set(l.name, l);
descend(); descend();
delete labels[l.name]; labels.del(l.name);
return true; // no descend again return true; // no descend again
} }
if (node instanceof AST_SymbolDeclaration) { if (node instanceof AST_SymbolDeclaration) {
@@ -115,7 +121,14 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
node.init_scope_vars(); node.init_scope_vars();
} }
if (node instanceof AST_SymbolLambda) { 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()); node.init.push(tw.parent());
} }
else if (node instanceof AST_SymbolDefun) { else if (node instanceof AST_SymbolDefun) {
@@ -144,7 +157,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
scope.def_variable(node); scope.def_variable(node);
} }
if (node instanceof AST_LabelRef) { if (node instanceof AST_LabelRef) {
var sym = labels[node.name]; var sym = labels.get(node.name);
if (!sym) throw new Error(string_template("Undefined label {name} [{line},{col}]", { if (!sym) throw new Error(string_template("Undefined label {name} [{line},{col}]", {
name: node.name, name: node.name,
line: node.start.line, line: node.start.line,
@@ -157,7 +170,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
// pass 2: find back references and eval // pass 2: find back references and eval
var func = null; var func = null;
var globals = self.globals = Object.create(null); var globals = self.globals = new Dictionary();
var tw = new TreeWalker(function(node, descend){ var tw = new TreeWalker(function(node, descend){
if (node instanceof AST_Lambda) { if (node instanceof AST_Lambda) {
var prev_func = func; var prev_func = func;
@@ -175,12 +188,12 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
var sym = node.scope.find_variable(name); var sym = node.scope.find_variable(name);
if (!sym) { if (!sym) {
var g; var g;
if (globals[name]) { if (globals.has(name)) {
g = globals[name]; g = globals.get(name);
} else { } else {
g = new SymbolDef(self, node); g = new SymbolDef(self, globals.size(), node);
g.undeclared = true; g.undeclared = true;
globals[name] = g; globals.set(name, g);
} }
node.thedef = g; node.thedef = g;
if (name == "eval" && tw.parent() instanceof AST_Call) { if (name == "eval" && tw.parent() instanceof AST_Call) {
@@ -200,15 +213,16 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
self.walk(tw); self.walk(tw);
}); });
AST_Scope.DEFMETHOD("init_scope_vars", function(){ AST_Scope.DEFMETHOD("init_scope_vars", function(nesting){
this.directives = []; // contains the directives defined in this scope, i.e. "use strict" this.directives = []; // contains the directives defined in this scope, i.e. "use strict"
this.variables = Object.create(null); // map name to AST_SymbolVar (variables defined in this scope; includes functions) this.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions)
this.functions = Object.create(null); // map name to AST_SymbolDefun (functions defined in this scope) this.functions = new Dictionary(); // map name to AST_SymbolDefun (functions defined in this scope)
this.uses_with = false; // will be set to true if this or some nested scope uses the `with` statement this.uses_with = false; // will be set to true if this or some nested scope uses the `with` statement
this.uses_eval = false; // will be set to true if this or nested scope uses the global `eval` this.uses_eval = false; // will be set to true if this or nested scope uses the global `eval`
this.parent_scope = null; // the parent scope this.parent_scope = null; // the parent scope
this.enclosed = []; // a list of variables from this or outer scope(s) that are referenced from this or inner scopes this.enclosed = []; // a list of variables from this or outer scope(s) that are referenced from this or inner scopes
this.cname = -1; // the current index for mangling functions/variables this.cname = -1; // the current index for mangling functions/variables
this.nesting = nesting; // the nesting level of this scope (0 means toplevel)
}); });
AST_Scope.DEFMETHOD("strict", function(){ AST_Scope.DEFMETHOD("strict", function(){
@@ -216,7 +230,7 @@ AST_Scope.DEFMETHOD("strict", function(){
}); });
AST_Lambda.DEFMETHOD("init_scope_vars", function(){ AST_Lambda.DEFMETHOD("init_scope_vars", function(){
AST_Scope.prototype.init_scope_vars.call(this); AST_Scope.prototype.init_scope_vars.apply(this, arguments);
this.uses_arguments = false; this.uses_arguments = false;
}); });
@@ -229,6 +243,7 @@ AST_SymbolRef.DEFMETHOD("reference", function() {
if (s === def.scope) break; if (s === def.scope) break;
s = s.parent_scope; s = s.parent_scope;
} }
this.frame = this.scope.nesting - def.scope.nesting;
}); });
AST_SymbolDeclaration.DEFMETHOD("init_scope_vars", function(){ AST_SymbolDeclaration.DEFMETHOD("init_scope_vars", function(){
@@ -245,7 +260,7 @@ AST_LabelRef.DEFMETHOD("reference", function(){
AST_Scope.DEFMETHOD("find_variable", function(name){ AST_Scope.DEFMETHOD("find_variable", function(name){
if (name instanceof AST_Symbol) name = name.name; if (name instanceof AST_Symbol) name = name.name;
return this.variables[name] return this.variables.get(name)
|| (this.parent_scope && this.parent_scope.find_variable(name)); || (this.parent_scope && this.parent_scope.find_variable(name));
}); });
@@ -255,23 +270,23 @@ AST_Scope.DEFMETHOD("has_directive", function(value){
}); });
AST_Scope.DEFMETHOD("def_function", function(symbol){ AST_Scope.DEFMETHOD("def_function", function(symbol){
this.functions[symbol.name] = this.def_variable(symbol); this.functions.set(symbol.name, this.def_variable(symbol));
}); });
AST_Scope.DEFMETHOD("def_variable", function(symbol){ AST_Scope.DEFMETHOD("def_variable", function(symbol){
var def; var def;
if (!this.variables[symbol.name]) { if (!this.variables.has(symbol.name)) {
def = new SymbolDef(this, symbol); def = new SymbolDef(this, this.variables.size(), symbol);
this.variables[symbol.name] = def; this.variables.set(symbol.name, def);
def.global = !this.parent_scope; def.global = !this.parent_scope;
} else { } else {
def = this.variables[symbol.name]; def = this.variables.get(symbol.name);
def.orig.push(symbol); def.orig.push(symbol);
} }
return symbol.thedef = def; return symbol.thedef = def;
}); });
AST_Scope.DEFMETHOD("next_mangled", function(){ AST_Scope.DEFMETHOD("next_mangled", function(options){
var ext = this.enclosed, n = ext.length; var ext = this.enclosed, n = ext.length;
out: while (true) { out: while (true) {
var m = base54(++this.cname); var m = base54(++this.cname);
@@ -281,7 +296,7 @@ AST_Scope.DEFMETHOD("next_mangled", function(){
// inner scopes. // inner scopes.
for (var i = n; --i >= 0;) { for (var i = n; --i >= 0;) {
var sym = ext[i]; var sym = ext[i];
var name = sym.mangled_name || (sym.unmangleable() && sym.name); var name = sym.mangled_name || (sym.unmangleable(options) && sym.name);
if (m == name) continue out; if (m == name) continue out;
} }
return m; return m;
@@ -293,8 +308,13 @@ AST_Scope.DEFMETHOD("references", function(sym){
return this.enclosed.indexOf(sym) < 0 ? null : sym; return this.enclosed.indexOf(sym) < 0 ? null : sym;
}); });
AST_Symbol.DEFMETHOD("unmangleable", function(){ AST_Symbol.DEFMETHOD("unmangleable", function(options){
return this.definition().unmangleable(); return this.definition().unmangleable(options);
});
// property accessors are not mangleable
AST_SymbolAccessor.DEFMETHOD("unmangleable", function(){
return true;
}); });
// labels are always mangleable // labels are always mangleable
@@ -327,10 +347,15 @@ AST_Symbol.DEFMETHOD("global", function(){
return this.definition().global; return this.definition().global;
}); });
AST_Toplevel.DEFMETHOD("mangle_names", function(options){ AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){
options = defaults(options, { return defaults(options, {
except : [] except : [],
eval : false,
}); });
});
AST_Toplevel.DEFMETHOD("mangle_names", function(options){
options = this._default_mangler_options(options);
// We only need to mangle declaration nodes. Special logic wired // We only need to mangle declaration nodes. Special logic wired
// into the code generator will display the mangled name if it's // into the code generator will display the mangled name if it's
// present (and for AST_SymbolRef-s it'll use the mangled name of // present (and for AST_SymbolRef-s it'll use the mangled name of
@@ -347,16 +372,11 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
} }
if (node instanceof AST_Scope) { if (node instanceof AST_Scope) {
var p = tw.parent(); var p = tw.parent();
var is_setget = p instanceof AST_ObjectSetter || p instanceof AST_ObjectGetter; node.variables.each(function(symbol){
var a = node.variables; if (options.except.indexOf(symbol.name) < 0) {
for (var i in a) { to_mangle.push(symbol);
var symbol = a[i];
if (!(is_setget && symbol instanceof AST_SymbolLambda)) {
if (options.except.indexOf(symbol.name) < 0) {
to_mangle.push(symbol);
}
} }
} });
return; return;
} }
if (node instanceof AST_Label) { if (node instanceof AST_Label) {
@@ -370,7 +390,8 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
to_mangle.forEach(function(def){ def.mangle(options) }); to_mangle.forEach(function(def){ def.mangle(options) });
}); });
AST_Toplevel.DEFMETHOD("compute_char_frequency", function(){ AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options){
options = this._default_mangler_options(options);
var tw = new TreeWalker(function(node){ var tw = new TreeWalker(function(node){
if (node instanceof AST_Constant) if (node instanceof AST_Constant)
base54.consider(node.print_to_string()); base54.consider(node.print_to_string());
@@ -428,7 +449,7 @@ AST_Toplevel.DEFMETHOD("compute_char_frequency", function(){
base54.consider("catch"); base54.consider("catch");
else if (node instanceof AST_Finally) else if (node instanceof AST_Finally)
base54.consider("finally"); base54.consider("finally");
else if (node instanceof AST_Symbol && node.unmangleable()) else if (node instanceof AST_Symbol && node.unmangleable(options))
base54.consider(node.name); base54.consider(node.name);
else if (node instanceof AST_Unary || node instanceof AST_Binary) else if (node instanceof AST_Unary || node instanceof AST_Binary)
base54.consider(node.operator); base54.consider(node.operator);

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) { function mergeSort(array, cmp) {
if (array.length < 2) return array.slice(); if (array.length < 2) return array.slice();
function merge(a, b) { function merge(a, b) {
@@ -238,3 +244,37 @@ function makePredicate(words) {
} }
return new Function("str", f); return new Function("str", f);
}; };
function Dictionary() {
this._values = Object.create(null);
this._size = 0;
};
Dictionary.prototype = {
set: function(key, val) {
if (!this.has(key)) ++this._size;
this._values["$" + key] = val;
return this;
},
get: function(key) { return this._values["$" + key] },
del: function(key) {
if (this.has(key)) {
--this._size;
delete this._values["$" + key];
}
return this;
},
has: function(key) { return ("$" + key) in this._values },
each: function(f) {
for (var i in this._values)
f(this._values[i], i.substr(1));
},
size: function() {
return this._size;
},
map: function(f) {
var ret = [];
for (var i in this._values)
ret.push(f(this._values[i], i.substr(1)));
return ret;
}
};

View File

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

123
test/compress/loops.js Normal file
View File

@@ -0,0 +1,123 @@
while_becomes_for: {
options = { loops: true };
input: {
while (foo()) bar();
}
expect: {
for (; foo(); ) bar();
}
}
drop_if_break_1: {
options = { loops: true };
input: {
for (;;)
if (foo()) break;
}
expect: {
for (; !foo(););
}
}
drop_if_break_2: {
options = { loops: true };
input: {
for (;bar();)
if (foo()) break;
}
expect: {
for (; bar() && !foo(););
}
}
drop_if_break_3: {
options = { loops: true };
input: {
for (;bar();) {
if (foo()) break;
stuff1();
stuff2();
}
}
expect: {
for (; bar() && !foo();) {
stuff1();
stuff2();
}
}
}
drop_if_break_4: {
options = { loops: true, sequences: true };
input: {
for (;bar();) {
x();
y();
if (foo()) break;
z();
k();
}
}
expect: {
for (; bar() && (x(), y(), !foo());) z(), k();
}
}
drop_if_else_break_1: {
options = { loops: true };
input: {
for (;;) if (foo()) bar(); else break;
}
expect: {
for (; foo(); ) bar();
}
}
drop_if_else_break_2: {
options = { loops: true };
input: {
for (;bar();) {
if (foo()) baz();
else break;
}
}
expect: {
for (; bar() && foo();) baz();
}
}
drop_if_else_break_3: {
options = { loops: true };
input: {
for (;bar();) {
if (foo()) baz();
else break;
stuff1();
stuff2();
}
}
expect: {
for (; bar() && foo();) {
baz();
stuff1();
stuff2();
}
}
}
drop_if_else_break_4: {
options = { loops: true, sequences: true };
input: {
for (;bar();) {
x();
y();
if (foo()) baz();
else break;
z();
k();
}
}
expect: {
for (; bar() && (x(), y(), foo());) baz(), z(), k();
}
}

View File

@@ -87,3 +87,75 @@ make_sequences_4: {
with (x = 5, obj); 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;
}
}
for_sequences: {
options = { sequences: true };
input: {
// 1
foo();
bar();
for (; false;);
// 2
foo();
bar();
for (x = 5; false;);
// 3
x = (foo in bar);
for (; false;);
// 4
x = (foo in bar);
for (y = 5; false;);
}
expect: {
// 1
for (foo(), bar(); false;);
// 2
for (foo(), bar(), x = 5; false;);
// 3
x = (foo in bar);
for (; false;);
// 4
x = (foo in bar);
for (y = 5; false;);
}
}

View File

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

View File

@@ -1,27 +1,14 @@
var save_stderr = process.stderr; var path = require("path");
var fs = require("fs"); 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;
});
var vm = require("vm"); var vm = require("vm");
var sys = require("util"); var sys = require("util");
var path = require("path");
var UglifyJS = vm.createContext({ var UglifyJS = vm.createContext({
sys : sys, sys : sys,
console : console, console : console,
MOZ_SourceMap : require("source-map") MOZ_SourceMap : require("source-map")
}); });
process.__defineGetter__("stderr", function(){
return save_stderr;
});
function load_global(file) { function load_global(file) {
file = path.resolve(path.dirname(module.filename), file); file = path.resolve(path.dirname(module.filename), file);
try { try {
@@ -65,7 +52,9 @@ for (var i in UglifyJS) {
exports.minify = function(files, options) { exports.minify = function(files, options) {
options = UglifyJS.defaults(options, { options = UglifyJS.defaults(options, {
outSourceMap : null, outSourceMap : null,
sourceRoot : null,
inSourceMap : null, inSourceMap : null,
fromString : false,
warnings : false, warnings : false,
}); });
if (typeof files == "string") if (typeof files == "string")
@@ -74,9 +63,11 @@ exports.minify = function(files, options) {
// 1. parse // 1. parse
var toplevel = null; var toplevel = null;
files.forEach(function(file){ files.forEach(function(file){
var code = fs.readFileSync(file, "utf8"); var code = options.fromString
? file
: fs.readFileSync(file, "utf8");
toplevel = UglifyJS.parse(code, { toplevel = UglifyJS.parse(code, {
filename: file, filename: options.fromString ? "?" : file,
toplevel: toplevel toplevel: toplevel
}); });
}); });
@@ -101,7 +92,8 @@ exports.minify = function(files, options) {
} }
if (options.outSourceMap) map = UglifyJS.SourceMap({ if (options.outSourceMap) map = UglifyJS.SourceMap({
file: options.outSourceMap, file: options.outSourceMap,
orig: inMap orig: inMap,
root: options.sourceRoot
}); });
var stream = UglifyJS.OutputStream({ source_map: map }); var stream = UglifyJS.OutputStream({ source_map: map });
toplevel.print(stream); toplevel.print(stream);