Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16953c2064 | ||
|
|
6b14f7c224 | ||
|
|
130c623be7 | ||
|
|
47c9895d59 | ||
|
|
ba403331c5 | ||
|
|
e82e89d1b0 | ||
|
|
83a4ebfedc | ||
|
|
9916d0e547 | ||
|
|
31c4a37e37 | ||
|
|
08219f0cee | ||
|
|
c4993e1e5c | ||
|
|
6064bea3db | ||
|
|
98978fc827 | ||
|
|
16430acc1f | ||
|
|
320c110b33 | ||
|
|
dbe33bbfc5 | ||
|
|
b5c3253b49 | ||
|
|
5cc90db7d0 | ||
|
|
f427e5efc7 | ||
|
|
e48802ad29 | ||
|
|
13c4dfcabd | ||
|
|
1abde9c8b0 | ||
|
|
4f555e2232 | ||
|
|
642ba2e92c | ||
|
|
089ac908b7 | ||
|
|
0d3fd2ef30 | ||
|
|
e98119496a | ||
|
|
bdfcbf496b | ||
|
|
dba8da4800 | ||
|
|
60c0f40250 | ||
|
|
e02771a5f2 | ||
|
|
f96f796f71 | ||
|
|
a9fa178f86 | ||
|
|
53355bdb24 | ||
|
|
f05c99d89f | ||
|
|
b49230ab8d | ||
|
|
78856a3dab | ||
|
|
1e5e13ed81 | ||
|
|
64270b9778 | ||
|
|
e312c5c2a7 | ||
|
|
1a5fd3e052 | ||
|
|
5a7e54cf72 | ||
|
|
39f8a62703 | ||
|
|
46be3f2bf1 | ||
|
|
258b46f4dc | ||
|
|
80da21dab4 | ||
|
|
bb0e4d7126 | ||
|
|
5276a4a873 | ||
|
|
a1ae0c8609 | ||
|
|
a90c1aeafe | ||
|
|
ff388a8d2d | ||
|
|
5346fb94bb | ||
|
|
a4f6d46118 | ||
|
|
7f5f4d60b7 | ||
|
|
ffccb233e5 | ||
|
|
fba0c1aafe | ||
|
|
774f2ded94 | ||
|
|
85af942d64 | ||
|
|
8413787efc | ||
|
|
dde57452aa | ||
|
|
cf409800be | ||
|
|
18270dd9f3 | ||
|
|
d4c25c571b | ||
|
|
5248b79506 | ||
|
|
abe0ebbf02 | ||
|
|
0852f5595e | ||
|
|
cb3cafa14d | ||
|
|
202fb93799 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
tmp/
|
||||
node_modules/
|
||||
|
||||
40
README.md
40
README.md
@@ -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
|
||||
@@ -79,6 +79,7 @@ The available options are:
|
||||
--export-all Only used when --wrap, this tells UglifyJS to add code to
|
||||
automatically export all globals. [boolean]
|
||||
-v, --verbose Verbose [boolean]
|
||||
-V, --version Print version number and exits. [boolean]
|
||||
|
||||
Specify `--output` (`-o`) to declare the output file. Otherwise the output
|
||||
goes to STDOUT.
|
||||
@@ -98,12 +99,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
|
||||
@@ -130,7 +131,7 @@ input files from the command line.
|
||||
## Mangler options
|
||||
|
||||
To enable the mangler you need to pass `--mangle` (`-m`). Optionally you
|
||||
can pass `-m sort` (we'll possibly have other flags in the future) in order
|
||||
can pass `-m sort=true` (we'll possibly have other flags in the future) in order
|
||||
to assign shorter names to most frequently used variables. This saves a few
|
||||
hundred bytes on jQuery before gzip, but the output is _bigger_ after gzip
|
||||
(and seems to happen for other libraries I tried it on) therefore it's not
|
||||
@@ -140,7 +141,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,13 +207,14 @@ 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
|
||||
code as usual. The possible downside of this approach is that the build
|
||||
will contain the `const` declarations.
|
||||
|
||||
<a name="codegen-options"></a>
|
||||
## Beautifier options
|
||||
|
||||
The code generator tries to output shortest code possible by default. In
|
||||
@@ -288,7 +290,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 +312,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)
|
||||
@@ -366,9 +368,19 @@ 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.
|
||||
|
||||
- `mangle` — pass `false` to skip mangling names.
|
||||
|
||||
- `output` (default `null`) — pass an object if you wish to specify
|
||||
additional [output options][codegen]. The defaults are optimized
|
||||
for best compression.
|
||||
|
||||
- `compress` (default `{}`) — pass `false` to skip compressing entirely.
|
||||
Pass an object to specify custom [compressor options][compressor].
|
||||
|
||||
We could add more options to `UglifyJS.minify` — if you need additional
|
||||
functionality please suggest!
|
||||
|
||||
@@ -516,3 +528,5 @@ The `source_map_options` (optional) can contain the following properties:
|
||||
[acorn]: https://github.com/marijnh/acorn
|
||||
[source-map]: https://github.com/mozilla/source-map
|
||||
[sm-spec]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit
|
||||
[codegen]: http://lisperator.net/uglifyjs/codegen
|
||||
[compressor]: http://lisperator.net/uglifyjs/compress
|
||||
|
||||
@@ -49,6 +49,7 @@ You need to pass an argument to this option to specify the name that your module
|
||||
.describe("export-all", "Only used when --wrap, this tells UglifyJS to add code to automatically export all globals.")
|
||||
.describe("lint", "Display some scope warnings")
|
||||
.describe("v", "Verbose")
|
||||
.describe("V", "Print version number and exit.")
|
||||
|
||||
.alias("p", "prefix")
|
||||
.alias("o", "output")
|
||||
@@ -58,6 +59,7 @@ You need to pass an argument to this option to specify the name that your module
|
||||
.alias("c", "compress")
|
||||
.alias("d", "define")
|
||||
.alias("r", "reserved")
|
||||
.alias("V", "version")
|
||||
|
||||
.string("source-map")
|
||||
.string("source-map-root")
|
||||
@@ -74,6 +76,7 @@ You need to pass an argument to this option to specify the name that your module
|
||||
.boolean("acorn")
|
||||
.boolean("spidermonkey")
|
||||
.boolean("lint")
|
||||
.boolean("V")
|
||||
|
||||
.wrap(80)
|
||||
|
||||
@@ -82,6 +85,12 @@ You need to pass an argument to this option to specify the name that your module
|
||||
|
||||
normalize(ARGS);
|
||||
|
||||
if (ARGS.version || ARGS.V) {
|
||||
var json = require("../package.json");
|
||||
sys.puts(json.name + ' ' + json.version);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (ARGS.ast_help) {
|
||||
var desc = UglifyJS.describe_ast();
|
||||
sys.puts(typeof desc == "string" ? desc : JSON.stringify(desc, null, 2));
|
||||
@@ -252,7 +261,7 @@ if (SCOPE_IS_NEEDED) {
|
||||
time_it("scope", function(){
|
||||
TOPLEVEL.figure_out_scope();
|
||||
if (MANGLE) {
|
||||
TOPLEVEL.compute_char_frequency();
|
||||
TOPLEVEL.compute_char_frequency(MANGLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
12
lib/ast.js
12
lib/ast.js
@@ -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))
|
||||
@@ -345,6 +345,10 @@ var AST_Lambda = DEFNODE("Lambda", "name argnames uses_arguments", {
|
||||
}
|
||||
}, AST_Scope);
|
||||
|
||||
var AST_Accessor = DEFNODE("Accessor", null, {
|
||||
$documentation: "A setter/getter function"
|
||||
}, AST_Lambda);
|
||||
|
||||
var AST_Function = DEFNODE("Function", null, {
|
||||
$documentation: "A function expression"
|
||||
}, AST_Lambda);
|
||||
@@ -758,6 +762,10 @@ var AST_Symbol = DEFNODE("Symbol", "scope name thedef", {
|
||||
$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", {
|
||||
$documentation: "A declaration symbol (symbol in var/const, function name or argument, symbol in catch)",
|
||||
$propdoc: {
|
||||
@@ -802,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",
|
||||
|
||||
376
lib/compress.js
376
lib/compress.js
@@ -53,6 +53,7 @@ function Compressor(options, false_by_default) {
|
||||
dead_code : !false_by_default,
|
||||
drop_debugger : !false_by_default,
|
||||
unsafe : !false_by_default,
|
||||
unsafe_comps : false,
|
||||
conditionals : !false_by_default,
|
||||
comparisons : !false_by_default,
|
||||
evaluate : !false_by_default,
|
||||
@@ -427,12 +428,23 @@ merge(Compressor.prototype, {
|
||||
var ret = [], prev = null;
|
||||
statements.forEach(function(stat){
|
||||
if (prev) {
|
||||
if (stat instanceof AST_For && stat.init && !(stat.init instanceof AST_Definitions)) {
|
||||
stat.init = cons_seq(stat.init);
|
||||
}
|
||||
else if (stat instanceof AST_For && !stat.init) {
|
||||
stat.init = prev.body;
|
||||
ret.pop();
|
||||
if (stat instanceof AST_For) {
|
||||
var opera = {};
|
||||
try {
|
||||
prev.body.walk(new TreeWalker(function(node){
|
||||
if (node instanceof AST_Binary && node.operator == "in")
|
||||
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) {
|
||||
stat.condition = cons_seq(stat.condition);
|
||||
@@ -542,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);
|
||||
@@ -692,7 +716,7 @@ merge(Compressor.prototype, {
|
||||
});
|
||||
def(AST_Binary, function(compressor){
|
||||
var self = this.clone(), op = this.operator;
|
||||
if (compressor.option("comparisons") && compressor.option("unsafe")) {
|
||||
if (compressor.option("unsafe_comps")) {
|
||||
switch (op) {
|
||||
case "<=" : self.operator = ">" ; return self;
|
||||
case "<" : self.operator = ">=" ; return self;
|
||||
@@ -802,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);
|
||||
});
|
||||
@@ -857,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;
|
||||
}
|
||||
@@ -893,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];
|
||||
@@ -999,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;
|
||||
}
|
||||
@@ -1014,7 +1056,7 @@ merge(Compressor.prototype, {
|
||||
if (hoist_funs || hoist_vars) {
|
||||
var dirs = [];
|
||||
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
|
||||
// space if we hoist `var` when there's only one.
|
||||
self.walk(new TreeWalker(function(node){
|
||||
@@ -1039,7 +1081,7 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
if (node instanceof AST_Var && hoist_vars) {
|
||||
node.definitions.forEach(function(def){
|
||||
vars[def.name.name] = def;
|
||||
vars.set(def.name.name, def);
|
||||
++vars_found;
|
||||
});
|
||||
var seq = node.to_assignments();
|
||||
@@ -1062,13 +1104,71 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
);
|
||||
self = self.transform(tt);
|
||||
if (vars_found > 0) hoisted.unshift(make_node(AST_Var, self, {
|
||||
definitions: Object.keys(vars).map(function(name){
|
||||
var def = vars[name].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;
|
||||
@@ -1106,6 +1206,61 @@ 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) {
|
||||
if_break_in_loop(self, compressor);
|
||||
self = make_node(AST_For, self, self).transform(compressor);
|
||||
}
|
||||
return self;
|
||||
});
|
||||
|
||||
OPT(AST_For, function(self, compressor){
|
||||
var cond = self.condition;
|
||||
if (cond) {
|
||||
@@ -1130,6 +1285,7 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
}
|
||||
}
|
||||
if_break_in_loop(self, compressor);
|
||||
return self;
|
||||
});
|
||||
|
||||
@@ -1268,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;
|
||||
});
|
||||
|
||||
@@ -1346,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);
|
||||
}
|
||||
}
|
||||
@@ -1372,7 +1601,7 @@ merge(Compressor.prototype, {
|
||||
case "Function":
|
||||
case "Error":
|
||||
case "Array":
|
||||
return make_node(AST_Call, self, self);
|
||||
return make_node(AST_Call, self, self).transform(compressor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1382,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()
|
||||
@@ -1434,9 +1674,9 @@ merge(Compressor.prototype, {
|
||||
compressor.warn("Boolean expression always true [{file}:{line},{col}]", self.start);
|
||||
return make_node(AST_True, self);
|
||||
}
|
||||
}
|
||||
if (e instanceof AST_Binary) {
|
||||
self = best_of(self, e.negate(compressor));
|
||||
if (e instanceof AST_Binary && self.operator == "!") {
|
||||
self = best_of(self, e.negate(compressor));
|
||||
}
|
||||
}
|
||||
return self.evaluate(compressor)[0];
|
||||
});
|
||||
@@ -1465,33 +1705,38 @@ merge(Compressor.prototype, {
|
||||
return this;
|
||||
});
|
||||
|
||||
var commutativeOperators = makePredicate("== === != !== * & | ^");
|
||||
|
||||
OPT(AST_Binary, function(self, compressor){
|
||||
function reverse(op) {
|
||||
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
|
||||
&& !(self.left instanceof AST_Constant)) {
|
||||
reverse();
|
||||
}
|
||||
}
|
||||
self = self.lift_sequences(compressor);
|
||||
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);
|
||||
}
|
||||
// XXX: intentionally falling down to the next case
|
||||
case "==":
|
||||
case "!=":
|
||||
if (self.left instanceof AST_UnaryPrefix
|
||||
&& self.left.operator == "typeof"
|
||||
&& self.right instanceof AST_String
|
||||
&& self.right.value == "undefined") {
|
||||
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.left instanceof AST_String
|
||||
&& self.left.value == "undefined"
|
||||
&& self.right instanceof AST_UnaryPrefix
|
||||
&& self.right.operator == "typeof") {
|
||||
if (!(self.right.expression instanceof AST_SymbolRef)
|
||||
|| !self.right.expression.undeclared()) {
|
||||
self.left = self.right.expression;
|
||||
@@ -1554,17 +1799,16 @@ merge(Compressor.prototype, {
|
||||
});
|
||||
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) {
|
||||
case "<": reverse(">"); break;
|
||||
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;
|
||||
});
|
||||
|
||||
|
||||
122
lib/output.js
122
lib/output.js
@@ -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;
|
||||
@@ -137,7 +138,7 @@ function OutputStream(options) {
|
||||
str = String(str);
|
||||
var ch = str.charAt(0);
|
||||
if (might_need_semicolon) {
|
||||
if (";}".indexOf(ch) < 0 && !/[;]$/.test(last)) {
|
||||
if ((!ch || ";}".indexOf(ch) < 0) && !/[;]$/.test(last)) {
|
||||
if (options.semicolons || requireSemicolonChars(ch)) {
|
||||
OUTPUT += ";";
|
||||
current_col++;
|
||||
@@ -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,23 +340,25 @@ function OutputStream(options) {
|
||||
/* -----[ utils ]----- */
|
||||
|
||||
function DEFPRINT(nodetype, generator) {
|
||||
nodetype.DEFMETHOD("print", function(stream){
|
||||
var self = this;
|
||||
stream.push_node(self);
|
||||
if (self.needs_parens(stream)) {
|
||||
stream.with_parens(function(){
|
||||
self.add_comments(stream);
|
||||
self.add_source_map(stream);
|
||||
generator(self, stream);
|
||||
});
|
||||
} else {
|
||||
nodetype.DEFMETHOD("_codegen", generator);
|
||||
};
|
||||
|
||||
AST_Node.DEFMETHOD("print", function(stream, force_parens){
|
||||
var self = this, generator = self._codegen;
|
||||
stream.push_node(self);
|
||||
if (force_parens || self.needs_parens(stream)) {
|
||||
stream.with_parens(function(){
|
||||
self.add_comments(stream);
|
||||
self.add_source_map(stream);
|
||||
generator(self, stream);
|
||||
}
|
||||
stream.pop_node();
|
||||
});
|
||||
};
|
||||
});
|
||||
} else {
|
||||
self.add_comments(stream);
|
||||
self.add_source_map(stream);
|
||||
generator(self, stream);
|
||||
}
|
||||
stream.pop_node();
|
||||
});
|
||||
|
||||
AST_Node.DEFMETHOD("print_to_string", function(options){
|
||||
var s = OutputStream(options);
|
||||
@@ -410,6 +425,11 @@ function OutputStream(options) {
|
||||
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){
|
||||
var p = output.parent();
|
||||
return p instanceof AST_Call // (foo, bar)() or foo(1, (2, 3), 4)
|
||||
@@ -449,24 +469,38 @@ 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)
|
||||
});
|
||||
|
||||
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;
|
||||
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_Call, function(output){
|
||||
var p = output.parent();
|
||||
return p instanceof AST_New && p.expression === this;
|
||||
});
|
||||
|
||||
PARENS(AST_New, function(output){
|
||||
var p = output.parent();
|
||||
// (new Date).getTime();
|
||||
if (p instanceof AST_Dot && no_constructor_parens(this, output))
|
||||
if (no_constructor_parens(this, output)
|
||||
&& (p instanceof AST_Dot // (new Date).getTime()
|
||||
|| p instanceof AST_Call && p.expression === this)) // (new foo)(bar)
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -529,6 +563,7 @@ function OutputStream(options) {
|
||||
});
|
||||
DEFPRINT(AST_Toplevel, function(self, output){
|
||||
display_body(self.body, true, output);
|
||||
output.print("");
|
||||
});
|
||||
DEFPRINT(AST_LabeledStatement, function(self, output){
|
||||
self.label.print(output);
|
||||
@@ -577,7 +612,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 {
|
||||
@@ -689,7 +728,7 @@ function OutputStream(options) {
|
||||
// to the inner IF). This function checks for this case and
|
||||
// adds the block brackets if needed.
|
||||
if (!self.body)
|
||||
return output.semicolon();
|
||||
return output.force_semicolon();
|
||||
if (self.body instanceof AST_Do
|
||||
&& output.option("ie_proof")) {
|
||||
// https://github.com/mishoo/UglifyJS/issues/#issue/57 IE
|
||||
@@ -713,7 +752,7 @@ function OutputStream(options) {
|
||||
}
|
||||
else break;
|
||||
}
|
||||
self.body.print(output);
|
||||
force_statement(self.body, output);
|
||||
};
|
||||
DEFPRINT(AST_If, function(self, output){
|
||||
output.print("if");
|
||||
@@ -821,13 +860,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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -846,7 +904,7 @@ function OutputStream(options) {
|
||||
DEFPRINT(AST_New, function(self, output){
|
||||
output.print("new");
|
||||
output.space();
|
||||
AST_Call.prototype.print.call(self, output);
|
||||
AST_Call.prototype._codegen(self, output);
|
||||
});
|
||||
|
||||
AST_Seq.DEFMETHOD("_do_print", function(output){
|
||||
|
||||
15
lib/parse.js
15
lib/parse.js
@@ -881,9 +881,14 @@ function parse($TEXT, options) {
|
||||
};
|
||||
|
||||
var function_ = function(in_statement, ctor) {
|
||||
var name = is("name") ? as_symbol(in_statement
|
||||
? AST_SymbolDefun
|
||||
: AST_SymbolLambda) : null;
|
||||
var is_accessor = ctor === AST_Accessor;
|
||||
var name = (is("name") ? as_symbol(in_statement
|
||||
? AST_SymbolDefun
|
||||
: is_accessor
|
||||
? AST_SymbolAccessor
|
||||
: AST_SymbolLambda)
|
||||
: is_accessor && (is("string") || is("num")) ? as_atom_node()
|
||||
: null);
|
||||
if (in_statement && !name)
|
||||
unexpected();
|
||||
expect("(");
|
||||
@@ -1158,7 +1163,7 @@ function parse($TEXT, options) {
|
||||
a.push(new AST_ObjectGetter({
|
||||
start : start,
|
||||
key : name,
|
||||
value : function_(false, AST_Lambda),
|
||||
value : function_(false, AST_Accessor),
|
||||
end : prev()
|
||||
}));
|
||||
continue;
|
||||
@@ -1167,7 +1172,7 @@ function parse($TEXT, options) {
|
||||
a.push(new AST_ObjectSetter({
|
||||
start : start,
|
||||
key : name,
|
||||
value : function_(false, AST_Lambda),
|
||||
value : function_(false, AST_Accessor),
|
||||
end : prev()
|
||||
}));
|
||||
continue;
|
||||
|
||||
132
lib/scope.js
132
lib/scope.js
@@ -43,7 +43,7 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
function SymbolDef(scope, orig) {
|
||||
function SymbolDef(scope, index, orig) {
|
||||
this.name = orig.name;
|
||||
this.orig = [ orig ];
|
||||
this.scope = scope;
|
||||
@@ -52,15 +52,18 @@ function SymbolDef(scope, orig) {
|
||||
this.mangled_name = null;
|
||||
this.undeclared = false;
|
||||
this.constant = false;
|
||||
this.index = index;
|
||||
};
|
||||
|
||||
SymbolDef.prototype = {
|
||||
unmangleable: function() {
|
||||
return this.global || this.undeclared || this.scope.uses_eval || this.scope.uses_with;
|
||||
unmangleable: function(options) {
|
||||
return this.global
|
||||
|| this.undeclared
|
||||
|| (!(options && options.eval) && (this.scope.uses_eval || this.scope.uses_with));
|
||||
},
|
||||
mangle: function() {
|
||||
if (!this.mangled_name && !this.unmangleable())
|
||||
this.mangled_name = this.scope.next_mangled();
|
||||
mangle: function(options) {
|
||||
if (!this.mangled_name && !this.unmangleable(options))
|
||||
this.mangled_name = this.scope.next_mangled(options);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -75,14 +78,20 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
|
||||
// pass 1: setup scope chaining and handle definitions
|
||||
var self = this;
|
||||
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){
|
||||
if (node instanceof AST_Scope) {
|
||||
node.init_scope_vars();
|
||||
node.init_scope_vars(nesting);
|
||||
var save_scope = node.parent_scope = scope;
|
||||
var save_labels = labels;
|
||||
++nesting;
|
||||
scope = node;
|
||||
labels = new Dictionary();
|
||||
descend();
|
||||
labels = save_labels;
|
||||
scope = save_scope;
|
||||
--nesting;
|
||||
return true; // don't descend again in TreeWalker
|
||||
}
|
||||
if (node instanceof AST_Directive) {
|
||||
@@ -97,16 +106,13 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
|
||||
}
|
||||
if (node instanceof AST_LabeledStatement) {
|
||||
var l = node.label;
|
||||
if (labels[l.name])
|
||||
if (labels.has(l.name))
|
||||
throw new Error(string_template("Label {name} defined twice", l));
|
||||
labels[l.name] = l;
|
||||
labels.set(l.name, l);
|
||||
descend();
|
||||
delete labels[l.name];
|
||||
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;
|
||||
}
|
||||
@@ -115,8 +121,13 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
|
||||
node.init_scope_vars();
|
||||
}
|
||||
if (node instanceof AST_SymbolLambda) {
|
||||
scope.def_function(node);
|
||||
node.init.push(tw.parent());
|
||||
//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);
|
||||
}
|
||||
else if (node instanceof AST_SymbolDefun) {
|
||||
// Careful here, the scope where this should be defined is
|
||||
@@ -125,14 +136,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
|
||||
@@ -144,7 +153,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
|
||||
scope.def_variable(node);
|
||||
}
|
||||
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}]", {
|
||||
name: node.name,
|
||||
line: node.start.line,
|
||||
@@ -157,7 +166,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
|
||||
|
||||
// pass 2: find back references and eval
|
||||
var func = null;
|
||||
var globals = self.globals = Object.create(null);
|
||||
var globals = self.globals = new Dictionary();
|
||||
var tw = new TreeWalker(function(node, descend){
|
||||
if (node instanceof AST_Lambda) {
|
||||
var prev_func = func;
|
||||
@@ -175,12 +184,12 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
|
||||
var sym = node.scope.find_variable(name);
|
||||
if (!sym) {
|
||||
var g;
|
||||
if (globals[name]) {
|
||||
g = globals[name];
|
||||
if (globals.has(name)) {
|
||||
g = globals.get(name);
|
||||
} else {
|
||||
g = new SymbolDef(self, node);
|
||||
g = new SymbolDef(self, globals.size(), node);
|
||||
g.undeclared = true;
|
||||
globals[name] = g;
|
||||
globals.set(name, g);
|
||||
}
|
||||
node.thedef = g;
|
||||
if (name == "eval" && tw.parent() instanceof AST_Call) {
|
||||
@@ -200,15 +209,16 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
|
||||
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.variables = Object.create(null); // 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.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions)
|
||||
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_eval = false; // will be set to true if this or nested scope uses the global `eval`
|
||||
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.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(){
|
||||
@@ -216,7 +226,7 @@ AST_Scope.DEFMETHOD("strict", 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;
|
||||
});
|
||||
|
||||
@@ -229,10 +239,7 @@ AST_SymbolRef.DEFMETHOD("reference", function() {
|
||||
if (s === def.scope) break;
|
||||
s = s.parent_scope;
|
||||
}
|
||||
});
|
||||
|
||||
AST_SymbolDeclaration.DEFMETHOD("init_scope_vars", function(){
|
||||
this.init = [];
|
||||
this.frame = this.scope.nesting - def.scope.nesting;
|
||||
});
|
||||
|
||||
AST_Label.DEFMETHOD("init_scope_vars", function(){
|
||||
@@ -245,7 +252,7 @@ AST_LabelRef.DEFMETHOD("reference", function(){
|
||||
|
||||
AST_Scope.DEFMETHOD("find_variable", function(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));
|
||||
});
|
||||
|
||||
@@ -255,23 +262,23 @@ AST_Scope.DEFMETHOD("has_directive", function(value){
|
||||
});
|
||||
|
||||
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){
|
||||
var def;
|
||||
if (!this.variables[symbol.name]) {
|
||||
def = new SymbolDef(this, symbol);
|
||||
this.variables[symbol.name] = def;
|
||||
if (!this.variables.has(symbol.name)) {
|
||||
def = new SymbolDef(this, this.variables.size(), symbol);
|
||||
this.variables.set(symbol.name, def);
|
||||
def.global = !this.parent_scope;
|
||||
} else {
|
||||
def = this.variables[symbol.name];
|
||||
def = this.variables.get(symbol.name);
|
||||
def.orig.push(symbol);
|
||||
}
|
||||
return symbol.thedef = def;
|
||||
});
|
||||
|
||||
AST_Scope.DEFMETHOD("next_mangled", function(){
|
||||
AST_Scope.DEFMETHOD("next_mangled", function(options){
|
||||
var ext = this.enclosed, n = ext.length;
|
||||
out: while (true) {
|
||||
var m = base54(++this.cname);
|
||||
@@ -281,7 +288,7 @@ AST_Scope.DEFMETHOD("next_mangled", function(){
|
||||
// inner scopes.
|
||||
for (var i = n; --i >= 0;) {
|
||||
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;
|
||||
}
|
||||
return m;
|
||||
@@ -293,8 +300,13 @@ AST_Scope.DEFMETHOD("references", function(sym){
|
||||
return this.enclosed.indexOf(sym) < 0 ? null : sym;
|
||||
});
|
||||
|
||||
AST_Symbol.DEFMETHOD("unmangleable", function(){
|
||||
return this.definition().unmangleable();
|
||||
AST_Symbol.DEFMETHOD("unmangleable", function(options){
|
||||
return this.definition().unmangleable(options);
|
||||
});
|
||||
|
||||
// property accessors are not mangleable
|
||||
AST_SymbolAccessor.DEFMETHOD("unmangleable", function(){
|
||||
return true;
|
||||
});
|
||||
|
||||
// labels are always mangleable
|
||||
@@ -327,10 +339,16 @@ AST_Symbol.DEFMETHOD("global", function(){
|
||||
return this.definition().global;
|
||||
});
|
||||
|
||||
AST_Toplevel.DEFMETHOD("mangle_names", function(options){
|
||||
options = defaults(options, {
|
||||
except : []
|
||||
AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){
|
||||
return defaults(options, {
|
||||
except : [],
|
||||
eval : false,
|
||||
sort : 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
|
||||
@@ -346,17 +364,16 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
|
||||
return true; // don't descend again in TreeWalker
|
||||
}
|
||||
if (node instanceof AST_Scope) {
|
||||
var p = tw.parent();
|
||||
var is_setget = p instanceof AST_ObjectSetter || p instanceof AST_ObjectGetter;
|
||||
var a = node.variables;
|
||||
for (var i in a) {
|
||||
var symbol = a[i];
|
||||
if (!(is_setget && symbol instanceof AST_SymbolLambda)) {
|
||||
if (options.except.indexOf(symbol.name) < 0) {
|
||||
to_mangle.push(symbol);
|
||||
}
|
||||
var p = tw.parent(), a = [];
|
||||
node.variables.each(function(symbol){
|
||||
if (options.except.indexOf(symbol.name) < 0) {
|
||||
a.push(symbol);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (options.sort) a.sort(function(a, b){
|
||||
return b.references.length - a.references.length;
|
||||
});
|
||||
to_mangle.push.apply(to_mangle, a);
|
||||
return;
|
||||
}
|
||||
if (node instanceof AST_Label) {
|
||||
@@ -370,7 +387,8 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(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){
|
||||
if (node instanceof AST_Constant)
|
||||
base54.consider(node.print_to_string());
|
||||
@@ -428,7 +446,7 @@ AST_Toplevel.DEFMETHOD("compute_char_frequency", function(){
|
||||
base54.consider("catch");
|
||||
else if (node instanceof AST_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);
|
||||
else if (node instanceof AST_Unary || node instanceof AST_Binary)
|
||||
base54.consider(node.operator);
|
||||
|
||||
@@ -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;
|
||||
|
||||
42
lib/utils.js
42
lib/utils.js
@@ -244,3 +244,45 @@ function makePredicate(words) {
|
||||
}
|
||||
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;
|
||||
},
|
||||
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)) {
|
||||
--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;
|
||||
}
|
||||
};
|
||||
|
||||
10
package.json
10
package.json
@@ -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.3",
|
||||
"version": "2.2.3",
|
||||
"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
31
test/compress/issue-44.js
Normal 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
30
test/compress/issue-59.js
Normal 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
123
test/compress/loops.js
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -127,3 +127,35 @@ lift_sequences_4: {
|
||||
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;);
|
||||
}
|
||||
}
|
||||
|
||||
210
test/compress/switch.js
Normal file
210
test/compress/switch.js
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,5 @@
|
||||
var path = require("path");
|
||||
var fs = require("fs");
|
||||
|
||||
// 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:
|
||||
path.existsSync = fs.existsSync;
|
||||
|
||||
var vm = require("vm");
|
||||
var sys = require("util");
|
||||
|
||||
@@ -67,6 +56,9 @@ exports.minify = function(files, options) {
|
||||
inSourceMap : null,
|
||||
fromString : false,
|
||||
warnings : false,
|
||||
mangle : {},
|
||||
output : null,
|
||||
compress : {}
|
||||
});
|
||||
if (typeof files == "string")
|
||||
files = [ files ];
|
||||
@@ -84,16 +76,20 @@ exports.minify = function(files, options) {
|
||||
});
|
||||
|
||||
// 2. compress
|
||||
toplevel.figure_out_scope();
|
||||
var sq = UglifyJS.Compressor({
|
||||
warnings: options.warnings,
|
||||
});
|
||||
toplevel = toplevel.transform(sq);
|
||||
if (options.compress) {
|
||||
var compress = { warnings: options.warnings };
|
||||
UglifyJS.merge(compress, options.compress);
|
||||
toplevel.figure_out_scope();
|
||||
var sq = UglifyJS.Compressor(compress);
|
||||
toplevel = toplevel.transform(sq);
|
||||
}
|
||||
|
||||
// 3. mangle
|
||||
toplevel.figure_out_scope();
|
||||
toplevel.compute_char_frequency();
|
||||
toplevel.mangle_names();
|
||||
if (options.mangle) {
|
||||
toplevel.figure_out_scope();
|
||||
toplevel.compute_char_frequency();
|
||||
toplevel.mangle_names(options.mangle);
|
||||
}
|
||||
|
||||
// 4. output
|
||||
var map = null;
|
||||
@@ -106,7 +102,11 @@ exports.minify = function(files, options) {
|
||||
orig: inMap,
|
||||
root: options.sourceRoot
|
||||
});
|
||||
var stream = UglifyJS.OutputStream({ source_map: map });
|
||||
var output = { source_map: map };
|
||||
if (options.output) {
|
||||
UglifyJS.merge(output, options.output);
|
||||
}
|
||||
var stream = UglifyJS.OutputStream(output);
|
||||
toplevel.print(stream);
|
||||
return {
|
||||
code : stream + "",
|
||||
|
||||
Reference in New Issue
Block a user