Compare commits

..

35 Commits

Author SHA1 Message Date
Mihai Bazon
6064bea3db v2.2.2 2012-12-06 14:25:18 +02:00
Mihai Bazon
98978fc827 Add proper parens in "NoIn" expressions.
fix #60.
2012-12-06 12:27:57 +02:00
Mihai Bazon
16430acc1f small improvement on merging assignments into hoisted vars 2012-12-05 13:14:49 +02:00
Mihai Bazon
320c110b33 When hoisting variables, try to merge in assignments that follow. 2012-12-05 12:30:25 +02:00
Mihai Bazon
dbe33bbfc5 Revert "Fixed reading from STDIN."
It breaks usage like this:

    echo '...code...' | uglifyjs

This reverts commit e48802ad29.
2012-11-30 11:33:50 +02:00
Mihai Bazon
b5c3253b49 Add test for issue #59 2012-11-30 11:26:37 +02:00
Mihai Bazon
5cc90db7d0 Don't messup compressor stack while optimizing Switch
Fix #59
2012-11-30 11:16:09 +02:00
Mihai Bazon
f427e5efc7 Merge pull request #58 from roxeteer/master
Fixed reading from STDIN
2012-11-29 01:23:07 -08:00
Visa Kopu
e48802ad29 Fixed reading from STDIN. 2012-11-29 10:51:15 +02:00
Mihai Bazon
13c4dfcabd fix #55 2012-11-24 10:02:08 +02:00
Mihai Bazon
1abde9c8b0 v2.2.1 2012-11-23 10:25:44 +02:00
Mihai Bazon
4f555e2232 fix for https://github.com/mishoo/UglifyJS/issues/474 2012-11-23 10:20:00 +02:00
Mihai Bazon
642ba2e92c rename the npm package to "uglify-js" and cli tool to "uglifyjs" 2012-11-21 13:27:03 +02:00
Mihai Bazon
089ac908b7 fix #51 2012-11-18 17:37:45 +02:00
Mihai Bazon
0d3fd2ef30 retain (1,eval) as is when it's the expression of an AST_Call
otherwise we change the meaning of eval from global to lexical.
2012-11-17 12:05:31 +02:00
Richard van Velzen
e98119496a Add support for somewhat preserving line numbers.
Usage: uglifyjs2 -b "beautify=0,preserve_line=1" /path/to/js

ref #46
2012-11-14 15:30:18 +02:00
Mihai Bazon
bdfcbf496b better solution for the last test in constant switch folding 2012-11-14 12:21:43 +02:00
Mihai Bazon
dba8da4800 optimize constant switch blocks
ref. mishoo/UglifyJS#441
2012-11-14 12:06:07 +02:00
Mihai Bazon
60c0f40250 Merge branch 'optimize_concat' of https://github.com/rvanvelzen/UglifyJS2 into rvanvelzen-optimize_concat 2012-11-13 14:38:55 +02:00
Mihai Bazon
e02771a5f2 don't change order in binary expressions if both operands have side effects 2012-11-13 14:32:07 +02:00
Richard van Velzen
f96f796f71 Add simple optimization for empty-string concats.
ref. #43
2012-11-12 15:41:03 +01:00
Mihai Bazon
a9fa178f86 v2.1.11 2012-11-12 13:24:52 +02:00
Mihai Bazon
53355bdb24 fix invalid AST produced by dropping unused variable
close #44
2012-11-12 13:23:57 +02:00
Mihai Bazon
f05c99d89f Merge pull request #41 from Skalman/toString-patch
Convert x.toString() to ""+x instead of x+""
2012-11-12 00:47:56 -08:00
Dan Wolff
b49230ab8d convert x.toString() to ""+x instead of x+""
In some places this can save one byte in whitespace, e.g. after return.
Example:

function f(arg) {
        // return""+arg - no space between return and ""
        return arg.toString();
}
2012-11-11 15:53:34 +02:00
Mihai Bazon
78856a3dab declare dependency versions
close #40
2012-11-09 16:43:49 +02:00
Mihai Bazon
1e5e13ed81 AST_LabelRef no longer inherits from AST_SymbolRef 2012-11-08 15:39:14 +02:00
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
14 changed files with 752 additions and 90 deletions

1
.gitignore vendored
View File

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

View File

@@ -14,7 +14,7 @@ Install
From NPM:
npm install uglify-js2
npm install uglify-js
From Git:
@@ -25,7 +25,7 @@ From Git:
Usage
-----
uglifyjs2 [input files] [options]
uglifyjs [input files] [options]
UglifyJS2 can take multiple input files. It's recommended that you pass the
input files first, then pass the options. UglifyJS will parse input files
@@ -98,12 +98,12 @@ map.
For example:
uglifyjs2 /home/doe/work/foo/src/js/file1.js \
/home/doe/work/foo/src/js/file2.js \
-o foo.min.js \
--source-map foo.min.js.map \
--source-map-root http://foo.com/src \
-p 5 -c -m
uglifyjs /home/doe/work/foo/src/js/file1.js \
/home/doe/work/foo/src/js/file2.js \
-o foo.min.js \
--source-map foo.min.js.map \
--source-map-root http://foo.com/src \
-p 5 -c -m
The above will compress and mangle `file1.js` and `file2.js`, will drop the
output in `foo.min.js` and the source map in `foo.min.js.map`. The source
@@ -140,7 +140,7 @@ When mangling is enabled but you want to prevent certain names from being
mangled, you can declare those names with `--reserved` (`-r`) — pass a
comma-separated list of names. For example:
uglifyjs2 ... -m -r '$,require,exports'
uglifyjs ... -m -r '$,require,exports'
to prevent the `require`, `exports` and `$` names from being changed.
@@ -206,7 +206,7 @@ separate file and include it into the build. For example you can have a
and build your code like this:
uglifyjs2 build/defines.js js/foo.js js/bar.js... -c
uglifyjs build/defines.js js/foo.js js/bar.js... -c
UglifyJS will notice the constants and, since they cannot be altered, it
will evaluate references to them to the value itself and drop unreachable
@@ -288,7 +288,7 @@ SpiderMonkey AST. It has a small CLI utility that parses one file and dumps
the AST in JSON on the standard output. To use UglifyJS to mangle and
compress that:
acorn file.js | uglifyjs2 --spidermonkey -m -c
acorn file.js | uglifyjs --spidermonkey -m -c
The `--spidermonkey` option tells UglifyJS that all input files are not
JavaScript, but JS code described in SpiderMonkey AST in JSON. Therefore we
@@ -310,7 +310,7 @@ API Reference
Assuming installation via NPM, you can load UglifyJS in your application
like this:
var UglifyJS = require("uglify-js2");
var UglifyJS = require("uglify-js");
It exports a lot of names, but I'll discuss here the basics that are needed
for parsing, mangling and compressing a piece of code. The sequence is (1)

View File

@@ -287,9 +287,9 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", {
},
wrap_commonjs: function(name, export_all) {
var self = this;
var to_export = [];
if (export_all) {
self.figure_out_scope();
var to_export = [];
self.walk(new TreeWalker(function(node){
if (node instanceof AST_SymbolDeclaration && node.definition().global) {
if (!find_if(function(n){ return n.name == node.name }, to_export))
@@ -810,7 +810,7 @@ var AST_SymbolRef = DEFNODE("SymbolRef", null, {
var AST_LabelRef = DEFNODE("LabelRef", null, {
$documentation: "Reference to a label symbol",
}, AST_SymbolRef);
}, AST_Symbol);
var AST_This = DEFNODE("This", null, {
$documentation: "The `this` symbol",

View File

@@ -554,12 +554,24 @@ merge(Compressor.prototype, {
def(AST_UnaryPrefix, function(){
return this.operator == "typeof";
});
def(AST_Binary, function(){
def(AST_Binary, function(compressor){
return this.operator == "+" &&
(this.left.is_string() || this.right.is_string());
(this.left.is_string(compressor) || this.right.is_string(compressor));
});
def(AST_Assign, function(){
return this.operator == "=" && this.right.is_string();
def(AST_Assign, function(compressor){
return (this.operator == "=" || this.operator == "+=") && this.right.is_string(compressor);
});
def(AST_Seq, function(compressor){
return this.cdr.is_string(compressor);
});
def(AST_Conditional, function(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){
node.DEFMETHOD("is_string", func);
@@ -814,10 +826,12 @@ merge(Compressor.prototype, {
(function(def){
def(AST_Statement, function(){ return null });
def(AST_Jump, function(){ return this });
def(AST_BlockStatement, function(){
function block_aborts(){
var n = this.body.length;
return n > 0 && aborts(this.body[n - 1]);
});
};
def(AST_BlockStatement, block_aborts);
def(AST_SwitchBranch, block_aborts);
def(AST_If, function(){
return this.alternative && aborts(this.body) && aborts(this.alternative);
});
@@ -869,23 +883,28 @@ merge(Compressor.prototype, {
&& !self.uses_eval
) {
var in_use = [];
var initializations = new Dictionary();
// pass 1: find out which symbols are directly used in
// this scope (not in nested scopes).
var scope = this;
var tw = new TreeWalker(function(node, descend){
if (node !== self) {
if (node instanceof AST_Defun) {
initializations.add(node.name.name, node);
return true; // don't go in nested scopes
}
if (node instanceof AST_Definitions && scope === self) {
node.definitions.forEach(function(def){
if (def.value && def.value.has_side_effects()) {
def.value.walk(tw);
if (def.value) {
initializations.add(def.name.name, def.value);
if (def.value.has_side_effects()) {
def.value.walk(tw);
}
}
});
return true;
}
if (node instanceof AST_SymbolRef && !(node instanceof AST_LabelRef)) {
if (node instanceof AST_SymbolRef) {
push_uniq(in_use, node.definition());
return true;
}
@@ -905,22 +924,20 @@ merge(Compressor.prototype, {
for (var i = 0; i < in_use.length; ++i) {
in_use[i].orig.forEach(function(decl){
// undeclared globals will be instanceof AST_SymbolRef
if (decl instanceof AST_SymbolDeclaration) {
decl.init.forEach(function(init){
var tw = new TreeWalker(function(node){
if (node instanceof AST_SymbolRef
&& !(node instanceof AST_LabelRef)) {
push_uniq(in_use, node.definition());
}
});
init.walk(tw);
var init = initializations.get(decl.name);
if (init) init.forEach(function(init){
var tw = new TreeWalker(function(node){
if (node instanceof AST_SymbolRef) {
push_uniq(in_use, node.definition());
}
});
}
init.walk(tw);
});
});
}
// pass 3: we should drop declarations not in_use
var tt = new TreeTransformer(
function before(node, descend) {
function before(node, descend, in_list) {
if (node instanceof AST_Lambda) {
for (var a = node.argnames, i = a.length; --i >= 0;) {
var sym = a[i];
@@ -1011,6 +1028,19 @@ merge(Compressor.prototype, {
}
return node;
}
if (node instanceof AST_For && node.init instanceof AST_BlockStatement) {
descend(node, this);
// certain combination of unused name + side effect leads to:
// https://github.com/mishoo/UglifyJS2/issues/44
// that's an invalid AST.
// We fix it at this stage by moving the `var` outside the `for`.
var body = node.init.body.slice(0, -1);
node.init = node.init.body.slice(-1)[0].body;
body.push(node);
return in_list ? MAP.splice(body) : make_node(AST_BlockStatement, node, {
body: body
});
}
if (node instanceof AST_Scope && node !== self)
return node;
}
@@ -1074,13 +1104,71 @@ merge(Compressor.prototype, {
}
);
self = self.transform(tt);
if (vars_found > 0) hoisted.unshift(make_node(AST_Var, self, {
definitions: vars.map(function(def){
def = def.clone();
def.value = null;
return def;
})
}));
if (vars_found > 0) {
// collect only vars which don't show up in self's arguments list
var defs = [];
vars.each(function(def, name){
if (self instanceof AST_Lambda
&& find_if(function(x){ return x.name == def.name.name },
self.argnames)) {
vars.del(name);
} else {
def = def.clone();
def.value = null;
defs.push(def);
vars.set(name, def);
}
});
if (defs.length > 0) {
// try to merge in assignments
for (var i = 0; i < self.body.length;) {
if (self.body[i] instanceof AST_SimpleStatement) {
var expr = self.body[i].body, sym, assign;
if (expr instanceof AST_Assign
&& expr.operator == "="
&& (sym = expr.left) instanceof AST_Symbol
&& vars.has(sym.name))
{
var def = vars.get(sym.name);
if (def.value) break;
def.value = expr.right;
remove(defs, def);
defs.push(def);
self.body.splice(i, 1);
continue;
}
if (expr instanceof AST_Seq
&& (assign = expr.car) instanceof AST_Assign
&& assign.operator == "="
&& (sym = assign.left) instanceof AST_Symbol
&& vars.has(sym.name))
{
var def = vars.get(sym.name);
if (def.value) break;
def.value = assign.right;
remove(defs, def);
defs.push(def);
self.body[i].body = expr.cdr;
continue;
}
}
if (self.body[i] instanceof AST_EmptyStatement) {
self.body.splice(i, 1);
continue;
}
if (self.body[i] instanceof AST_BlockStatement) {
var tmp = [ i, 1 ].concat(self.body[i].body);
self.body.splice.apply(self.body, tmp);
continue;
}
break;
}
defs = make_node(AST_Var, self, {
definitions: defs
});
hoisted.push(defs);
};
}
self.body = dirs.concat(hoisted, self.body);
}
return self;
@@ -1118,11 +1206,57 @@ merge(Compressor.prototype, {
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) {
self = make_node(AST_For, self, self);
if_break_in_loop(self, compressor);
self = make_node(AST_For, self, self).transform(compressor);
}
return self;
});
@@ -1151,6 +1285,7 @@ merge(Compressor.prototype, {
}
}
}
if_break_in_loop(self, compressor);
return self;
});
@@ -1289,6 +1424,79 @@ merge(Compressor.prototype, {
if (stat instanceof AST_Break && loop_body(compressor.loopcontrol_target(stat.label)) === self)
last_branch.body.pop();
}
var exp = self.expression.evaluate(compressor);
out: if (exp.length == 2) try {
// constant expression
self.expression = exp[0];
if (!compressor.option("dead_code")) break out;
var value = exp[1];
var in_if = false;
var in_block = false;
var started = false;
var stopped = false;
var ruined = false;
var tt = new TreeTransformer(function(node, descend, in_list){
if (node instanceof AST_Lambda || node instanceof AST_SimpleStatement) {
// no need to descend these node types
return node;
}
else if (node instanceof AST_Switch && node === self) {
node = node.clone();
descend(node, this);
return ruined ? node : make_node(AST_BlockStatement, node, {
body: node.body.reduce(function(a, branch){
return a.concat(branch.body);
}, [])
}).transform(compressor);
}
else if (node instanceof AST_If || node instanceof AST_Try) {
var save = in_if;
in_if = !in_block;
descend(node, this);
in_if = save;
return node;
}
else if (node instanceof AST_StatementWithBody || node instanceof AST_Switch) {
var save = in_block;
in_block = true;
descend(node, this);
in_block = save;
return node;
}
else if (node instanceof AST_Break && this.loopcontrol_target(node.label) === self) {
if (in_if) {
ruined = true;
return node;
}
if (in_block) return node;
stopped = true;
return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
}
else if (node instanceof AST_SwitchBranch && this.parent() === self) {
if (stopped) return MAP.skip;
if (node instanceof AST_Case) {
var exp = node.expression.evaluate(compressor);
if (exp.length < 2) {
// got a case with non-constant expression, baling out
throw self;
}
if (exp[1] === value || started) {
started = true;
if (aborts(node)) stopped = true;
descend(node, this);
return node;
}
return MAP.skip;
}
descend(node, this);
return node;
}
});
tt.stack = compressor.stack.slice(); // so that's able to see parent nodes
self = self.transform(tt);
} catch(ex) {
if (ex !== self) throw ex;
}
return self;
});
@@ -1367,9 +1575,9 @@ merge(Compressor.prototype, {
}
else if (exp instanceof AST_Dot && exp.property == "toString" && self.args.length == 0) {
return make_node(AST_Binary, self, {
left: exp.expression,
left: make_node(AST_String, self, { value: "" }),
operator: "+",
right: make_node(AST_String, self, { value: "" })
right: exp.expression
}).transform(compressor);
}
}
@@ -1403,8 +1611,19 @@ 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 (!self.car.has_side_effects()) {
// we shouldn't compress (1,eval)(something) to
// eval(something) because that changes the meaning of
// eval (becomes lexical instead of global).
var p;
if (!(self.cdr instanceof AST_SymbolRef
&& self.cdr.name == "eval"
&& self.cdr.undeclared()
&& (p = compressor.parent()) instanceof AST_Call
&& p.expression === self)) {
return self.cdr;
}
}
if (compressor.option("cascade")) {
if (self.car instanceof AST_Assign
&& !self.car.left.has_side_effects()
@@ -1490,10 +1709,12 @@ merge(Compressor.prototype, {
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 (!(self.left.has_side_effects() && self.right.has_side_effects())) {
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
@@ -1505,7 +1726,7 @@ merge(Compressor.prototype, {
if (compressor.option("comparisons")) switch (self.operator) {
case "===":
case "!==":
if ((self.left.is_string() && self.right.is_string()) ||
if ((self.left.is_string(compressor) && self.right.is_string(compressor)) ||
(self.left.is_boolean() && self.right.is_boolean())) {
self.operator = self.operator.substr(0, 2);
}
@@ -1583,6 +1804,11 @@ merge(Compressor.prototype, {
case "<=": reverse(">="); break;
}
}
if (self.operator == "+" && self.right instanceof AST_String
&& self.right.getValue() === "" && self.left instanceof AST_Binary
&& self.left.operator == "+" && self.left.is_string(compressor)) {
return self.left;
}
return self;
});

View File

@@ -59,7 +59,8 @@ function OutputStream(options) {
source_map : null,
bracketize : false,
semicolons : true,
comments : false
comments : false,
preserve_line : false
}, true);
var indentation = 0;
@@ -154,6 +155,18 @@ function OutputStream(options) {
might_need_semicolon = false;
maybe_newline();
}
if (!options.beautify && options.preserve_line && stack[stack.length - 1]) {
var target_line = stack[stack.length - 1].start.line;
while (current_line < target_line) {
OUTPUT += "\n";
current_pos++;
current_line++;
current_col = 0;
might_need_space = false;
}
}
if (might_need_space) {
var prev = last_char();
if ((is_identifier_char(prev)
@@ -327,10 +340,10 @@ function OutputStream(options) {
/* -----[ utils ]----- */
function DEFPRINT(nodetype, generator) {
nodetype.DEFMETHOD("print", function(stream){
nodetype.DEFMETHOD("print", function(stream, force_parens){
var self = this;
stream.push_node(self);
if (self.needs_parens(stream)) {
if (force_parens || self.needs_parens(stream)) {
stream.with_parens(function(){
self.add_comments(stream);
self.add_source_map(stream);
@@ -412,7 +425,7 @@ function OutputStream(options) {
PARENS(AST_Unary, function(output){
var p = output.parent();
return p instanceof AST_PropAccess;
return p instanceof AST_PropAccess && p.expression === this;
});
PARENS(AST_Seq, function(output){
@@ -454,23 +467,26 @@ function OutputStream(options) {
return true;
}
}
// for (var i = (foo in bar);;); ← perhaps useless, but valid syntax
if (this.operator == "in") {
// the “NoIn” stuff :-\
// UglifyJS 1.3.3 misses this one.
if ((p instanceof AST_For || p instanceof AST_ForIn) && p.init === this)
return true;
if (p instanceof AST_VarDef) {
var v = output.parent(1), p2 = output.parent(2);
if ((p2 instanceof AST_For || p2 instanceof AST_ForIn) && p2.init === v)
return true;
}
}
});
PARENS(AST_PropAccess, function(output){
var p = output.parent();
return p instanceof AST_New && p.expression === this;
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){
@@ -594,7 +610,11 @@ function OutputStream(options) {
output.space();
output.with_parens(function(){
if (self.init) {
self.init.print(output);
if (self.init instanceof AST_Definitions) {
self.init.print(output);
} else {
parenthesize_for_noin(self.init, output, true);
}
output.print(";");
output.space();
} else {
@@ -838,13 +858,32 @@ function OutputStream(options) {
DEFPRINT(AST_Const, function(self, output){
self._do_print(output, "const");
});
function parenthesize_for_noin(node, output, noin) {
if (!noin) node.print(output);
else try {
// need to take some precautions here:
// https://github.com/mishoo/UglifyJS2/issues/60
node.walk(new TreeWalker(function(node){
if (node instanceof AST_Binary && node.operator == "in")
throw output;
}));
node.print(output);
} catch(ex) {
if (ex !== output) throw ex;
node.print(output, true);
}
};
DEFPRINT(AST_VarDef, function(self, output){
self.name.print(output);
if (self.value) {
output.space();
output.print("=");
output.space();
self.value.print(output);
var p = output.parent(1);
var noin = p instanceof AST_For || p instanceof AST_ForIn;
parenthesize_for_noin(self.value, output, noin);
}
});

View File

@@ -59,7 +59,7 @@ SymbolDef.prototype = {
unmangleable: function(options) {
return this.global
|| this.undeclared
|| (!options.eval && (this.scope.uses_eval || this.scope.uses_with));
|| (!(options && options.eval) && (this.scope.uses_eval || this.scope.uses_with));
},
mangle: function(options) {
if (!this.mangled_name && !this.unmangleable(options))
@@ -110,9 +110,6 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
labels.del(l.name);
return true; // no descend again
}
if (node instanceof AST_SymbolDeclaration) {
node.init_scope_vars();
}
if (node instanceof AST_Symbol) {
node.scope = scope;
}
@@ -128,8 +125,6 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
// 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) {
// Careful here, the scope where this should be defined is
@@ -138,14 +133,12 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
// instanceof AST_Scope) but we get to the symbol a bit
// later.
(node.scope = scope.parent_scope).def_function(node);
node.init.push(tw.parent());
}
else if (node instanceof AST_SymbolVar
|| node instanceof AST_SymbolConst) {
var def = scope.def_variable(node);
def.constant = node instanceof AST_SymbolConst;
def = tw.parent();
if (def.value) node.init.push(def);
}
else if (node instanceof AST_SymbolCatch) {
// XXX: this is wrong according to ECMA-262 (12.4). the
@@ -246,10 +239,6 @@ AST_SymbolRef.DEFMETHOD("reference", function() {
this.frame = this.scope.nesting - def.scope.nesting;
});
AST_SymbolDeclaration.DEFMETHOD("init_scope_vars", function(){
this.init = [];
});
AST_Label.DEFMETHOD("init_scope_vars", function(){
this.references = [];
});
@@ -347,11 +336,15 @@ AST_Symbol.DEFMETHOD("global", function(){
return this.definition().global;
});
AST_Toplevel.DEFMETHOD("mangle_names", function(options){
options = defaults(options, {
AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){
return defaults(options, {
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
// 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
@@ -387,6 +380,7 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
});
AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options){
options = this._default_mangler_options(options);
var tw = new TreeWalker(function(node){
if (node instanceof AST_Constant)
base54.consider(node.print_to_string());

View File

@@ -65,7 +65,7 @@ TreeTransformer.prototype = new TreeWalker;
x = this;
descend(x, tw);
} else {
tw.stack[tw.stack - 1] = x = this.clone();
tw.stack[tw.stack.length - 1] = x = this.clone();
descend(x, tw);
y = tw.after(x, in_list);
if (y !== undefined) x = y;

View File

@@ -255,6 +255,14 @@ Dictionary.prototype = {
this._values["$" + key] = val;
return this;
},
add: function(key, val) {
if (this.has(key)) {
this.get(key).push(val);
} else {
this.set(key, [ val ]);
}
return this;
},
get: function(key) { return this._values["$" + key] },
del: function(key) {
if (this.has(key)) {

View File

@@ -1,9 +1,9 @@
{
"name": "uglify-js2",
"name": "uglify-js",
"description": "JavaScript parser, mangler/compressor and beautifier toolkit",
"homepage": "http://lisperator.net/uglifyjs",
"main": "tools/node.js",
"version": "2.1.7",
"version": "2.2.2",
"engines": { "node" : ">=0.4.0" },
"maintainers": [{
"name": "Mihai Bazon",
@@ -15,11 +15,11 @@
"url": "https://github.com/mishoo/UglifyJS2.git"
}],
"dependencies": {
"source-map" : "*",
"optimist" : "*"
"source-map" : "~0.1.7",
"optimist" : "~0.3.5"
},
"bin": {
"uglifyjs2" : "bin/uglifyjs2"
"uglifyjs" : "bin/uglifyjs"
},
"scripts": {"test": "node test/run-tests.js"}
}

31
test/compress/issue-44.js Normal file
View File

@@ -0,0 +1,31 @@
issue_44_valid_ast_1: {
options = { unused: true };
input: {
function a(b) {
for (var i = 0, e = b.qoo(); ; i++) {}
}
}
expect: {
function a(b) {
var i = 0;
for (b.qoo(); ; i++);
}
}
}
issue_44_valid_ast_2: {
options = { unused: true };
input: {
function a(b) {
if (foo) for (var i = 0, e = b.qoo(); ; i++) {}
}
}
expect: {
function a(b) {
if (foo) {
var i = 0;
for (b.qoo(); ; i++);
}
}
}
}

30
test/compress/issue-59.js Normal file
View File

@@ -0,0 +1,30 @@
keep_continue: {
options = {
dead_code: true,
evaluate: true
};
input: {
while (a) {
if (b) {
switch (true) {
case c():
d();
}
continue;
}
f();
}
}
expect: {
while (a) {
if (b) {
switch (true) {
case c():
d();
}
continue;
}
f();
}
}
}

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();
}
}

210
test/compress/switch.js Normal file
View File

@@ -0,0 +1,210 @@
constant_switch_1: {
options = { dead_code: true, evaluate: true };
input: {
switch (1+1) {
case 1: foo(); break;
case 1+1: bar(); break;
case 1+1+1: baz(); break;
}
}
expect: {
bar();
}
}
constant_switch_2: {
options = { dead_code: true, evaluate: true };
input: {
switch (1) {
case 1: foo();
case 1+1: bar(); break;
case 1+1+1: baz();
}
}
expect: {
foo();
bar();
}
}
constant_switch_3: {
options = { dead_code: true, evaluate: true };
input: {
switch (10) {
case 1: foo();
case 1+1: bar(); break;
case 1+1+1: baz();
default:
def();
}
}
expect: {
def();
}
}
constant_switch_4: {
options = { dead_code: true, evaluate: true };
input: {
switch (2) {
case 1:
x();
if (foo) break;
y();
break;
case 1+1:
bar();
default:
def();
}
}
expect: {
bar();
def();
}
}
constant_switch_5: {
options = { dead_code: true, evaluate: true };
input: {
switch (1) {
case 1:
x();
if (foo) break;
y();
break;
case 1+1:
bar();
default:
def();
}
}
expect: {
// the break inside the if ruins our job
// we can still get rid of irrelevant cases.
switch (1) {
case 1:
x();
if (foo) break;
y();
}
// XXX: we could optimize this better by inventing an outer
// labeled block, but that's kinda tricky.
}
}
constant_switch_6: {
options = { dead_code: true, evaluate: true };
input: {
OUT: {
foo();
switch (1) {
case 1:
x();
if (foo) break OUT;
y();
case 1+1:
bar();
break;
default:
def();
}
}
}
expect: {
OUT: {
foo();
x();
if (foo) break OUT;
y();
bar();
}
}
}
constant_switch_7: {
options = { dead_code: true, evaluate: true };
input: {
OUT: {
foo();
switch (1) {
case 1:
x();
if (foo) break OUT;
for (var x = 0; x < 10; x++) {
if (x > 5) break; // this break refers to the for, not to the switch; thus it
// shouldn't ruin our optimization
console.log(x);
}
y();
case 1+1:
bar();
break;
default:
def();
}
}
}
expect: {
OUT: {
foo();
x();
if (foo) break OUT;
for (var x = 0; x < 10; x++) {
if (x > 5) break;
console.log(x);
}
y();
bar();
}
}
}
constant_switch_8: {
options = { dead_code: true, evaluate: true };
input: {
OUT: switch (1) {
case 1:
x();
for (;;) break OUT;
y();
break;
case 1+1:
bar();
default:
def();
}
}
expect: {
OUT: {
x();
for (;;) break OUT;
y();
}
}
}
constant_switch_9: {
options = { dead_code: true, evaluate: true };
input: {
OUT: switch (1) {
case 1:
x();
for (;;) if (foo) break OUT;
y();
case 1+1:
bar();
default:
def();
}
}
expect: {
OUT: {
x();
for (;;) if (foo) break OUT;
y();
bar();
def();
}
}
}