Compare commits
138 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e6266136d | ||
|
|
5c22a1bdf5 | ||
|
|
9794ebf88c | ||
|
|
68394eed93 | ||
|
|
753b4b6cc8 | ||
|
|
a9c1b9f138 | ||
|
|
5af144522a | ||
|
|
483e0cadfb | ||
|
|
4b818056cf | ||
|
|
b956e5f1d9 | ||
|
|
37d7cb8565 | ||
|
|
2b8e206fec | ||
|
|
a869b854fa | ||
|
|
81f5efe39a | ||
|
|
69dde0462b | ||
|
|
7628bcac01 | ||
|
|
75f0bbe6e8 | ||
|
|
478bf4dbdd | ||
|
|
e0f67baf2d | ||
|
|
b14d3df3d2 | ||
|
|
24e58ee70c | ||
|
|
9b1a40dfc3 | ||
|
|
e4b078cff7 | ||
|
|
3bd7ca9961 | ||
|
|
f83aca65b7 | ||
|
|
aebafad41e | ||
|
|
26746ce316 | ||
|
|
dac6efb43d | ||
|
|
8880f4824c | ||
|
|
cb0c576bdd | ||
|
|
3a591c43fe | ||
|
|
db66eca958 | ||
|
|
f2767452e6 | ||
|
|
916faf0a48 | ||
|
|
f36e4e9a78 | ||
|
|
fdf8b5eb71 | ||
|
|
de7ec7f1b7 | ||
|
|
3c8a0bdff4 | ||
|
|
9e8ba27dcd | ||
|
|
719a8fd102 | ||
|
|
3a22e917de | ||
|
|
a9af2c9e62 | ||
|
|
31e99cebe7 | ||
|
|
a5b209470c | ||
|
|
e9a571b2a1 | ||
|
|
8bf83f42ea | ||
|
|
522566ea80 | ||
|
|
297af47c89 | ||
|
|
faa354f5ca | ||
|
|
1529ab965a | ||
|
|
605f330e69 | ||
|
|
f0909bdc8f | ||
|
|
c13e7e621d | ||
|
|
ad071f8017 | ||
|
|
c058d8b9cd | ||
|
|
1d8871a092 | ||
|
|
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 | ||
|
|
7b87d2ef83 | ||
|
|
70fd2b1f33 | ||
|
|
30faaf13ed | ||
|
|
41be8632d3 | ||
|
|
bee01dc1be | ||
|
|
12f71e01d0 | ||
|
|
3a72deacab | ||
|
|
fc8314e810 | ||
|
|
11dffe950e | ||
|
|
6f45928a73 | ||
|
|
afb7faa6fa | ||
|
|
6aa56f92fe | ||
|
|
4fe4257c69 | ||
|
|
a5e75c5a21 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
tmp/
|
||||
node_modules/
|
||||
|
||||
29
LICENSE
Normal file
29
LICENSE
Normal file
@@ -0,0 +1,29 @@
|
||||
UglifyJS is released under the BSD license:
|
||||
|
||||
Copyright 2012-2013 (c) Mihai Bazon <mihai.bazon@gmail.com>
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials
|
||||
provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
|
||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGE.
|
||||
124
README.md
124
README.md
@@ -12,9 +12,16 @@ Chrome and probably Safari).
|
||||
Install
|
||||
-------
|
||||
|
||||
From NPM:
|
||||
First make sure you have installed the latest version of [node.js](http://nodejs.org/)
|
||||
(You may need to restart your computer after this step).
|
||||
|
||||
npm install uglify-js2
|
||||
From NPM for use as a command line app:
|
||||
|
||||
npm install uglify-js -g
|
||||
|
||||
From NPM for programmatic use:
|
||||
|
||||
npm install uglify-js
|
||||
|
||||
From Git:
|
||||
|
||||
@@ -25,7 +32,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
|
||||
@@ -42,8 +49,14 @@ The available options are:
|
||||
[string]
|
||||
--source-map-root The path to the original source to be included in the
|
||||
source map. [string]
|
||||
--source-map-url The path to the source map to be added in //@
|
||||
sourceMappingURL. Defaults to the value passed with
|
||||
--source-map. [string]
|
||||
--in-source-map Input source map, useful if you're compressing JS that was
|
||||
generated from some other original code.
|
||||
--screw-ie8 Pass this flag if you don't care about full compliance with
|
||||
Internet Explorer 6-8 quirks (by default UglifyJS will try
|
||||
to be IE-proof).
|
||||
-p, --prefix Skip prefix for original filenames that appear in source
|
||||
maps. For example -p 3 will drop 3 directories from file
|
||||
names and ensure they are relative paths.
|
||||
@@ -78,7 +91,9 @@ The available options are:
|
||||
[string]
|
||||
--export-all Only used when --wrap, this tells UglifyJS to add code to
|
||||
automatically export all globals. [boolean]
|
||||
--lint Display some scope warnings [boolean]
|
||||
-v, --verbose Verbose [boolean]
|
||||
-V, --version Print version number and exit. [boolean]
|
||||
|
||||
Specify `--output` (`-o`) to declare the output file. Otherwise the output
|
||||
goes to STDOUT.
|
||||
@@ -98,12 +113,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
|
||||
@@ -129,18 +144,25 @@ 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
|
||||
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
|
||||
enabled by default.
|
||||
To enable the mangler you need to pass `--mangle` (`-m`). The following
|
||||
(comma-separated) options are supported:
|
||||
|
||||
- `sort` — 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 enabled by default.
|
||||
|
||||
- `toplevel` — mangle names declared in the toplevel scope (disabled by
|
||||
default).
|
||||
|
||||
- `eval` — mangle names visible in scopes where `eval` or `when` are used
|
||||
(disabled by default).
|
||||
|
||||
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.
|
||||
|
||||
@@ -151,15 +173,12 @@ you can pass a comma-separated list of options. Options are in the form
|
||||
`foo=bar`, or just `foo` (the latter implies a boolean option that you want
|
||||
to set `true`; it's effectively a shortcut for `foo=true`).
|
||||
|
||||
The defaults should be tuned for maximum compression on most code. Here are
|
||||
the available options (all are `true` by default, except `hoist_vars`):
|
||||
|
||||
- `sequences` -- join consecutive simple statements using the comma operator
|
||||
- `properties` -- rewrite property access using the dot notation, for
|
||||
example `foo["bar"] → foo.bar`
|
||||
- `dead-code` -- remove unreachable code
|
||||
- `drop-debugger` -- remove `debugger;` statements
|
||||
- `unsafe` -- apply "unsafe" transformations (discussion below)
|
||||
- `dead_code` -- remove unreachable code
|
||||
- `drop_debugger` -- remove `debugger;` statements
|
||||
- `unsafe` (default: false) -- apply "unsafe" transformations (discussion below)
|
||||
- `conditionals` -- apply optimizations for `if`-s and conditional
|
||||
expressions
|
||||
- `comparisons` -- apply certain optimizations to binary nodes, for example:
|
||||
@@ -171,16 +190,32 @@ the available options (all are `true` by default, except `hoist_vars`):
|
||||
- `loops` -- optimizations for `do`, `while` and `for` loops when we can
|
||||
statically determine the condition
|
||||
- `unused` -- drop unreferenced functions and variables
|
||||
- `hoist-funs` -- hoist function declarations
|
||||
- `hoist-vars` -- hoist `var` declarations (this is `false` by default
|
||||
because it seems to increase the size of the output in general)
|
||||
- `if-return` -- optimizations for if/return and if/continue
|
||||
- `join-vars` -- join consecutive `var` statements
|
||||
- `hoist_funs` -- hoist function declarations
|
||||
- `hoist_vars` (default: false) -- hoist `var` declarations (this is `false`
|
||||
by default because it seems to increase the size of the output in general)
|
||||
- `if_return` -- optimizations for if/return and if/continue
|
||||
- `join_vars` -- join consecutive `var` statements
|
||||
- `cascade` -- small optimization for sequences, transform `x, x` into `x`
|
||||
and `x = something(), x` into `x = something()`
|
||||
- `warnings` -- display warnings when dropping unreachable code or unused
|
||||
declarations etc.
|
||||
|
||||
### The `unsafe` option
|
||||
|
||||
It enables some transformations that *might* break code logic in certain
|
||||
contrived cases, but should be fine for most code. You might want to try it
|
||||
on your own code, it should reduce the minified size. Here's what happens
|
||||
when this flag is on:
|
||||
|
||||
- `new Array(1, 2, 3)` or `Array(1, 2, 3)` → `[1, 2, 3 ]`
|
||||
- `new Object()` → `{}`
|
||||
- `String(exp)` or `exp.toString()` → `"" + exp`
|
||||
- `new Object/RegExp/Function/Error/Array (...)` → we discard the `new`
|
||||
- `typeof foo == "undefined"` → `foo === void 0`
|
||||
- `void 0` → `"undefined"` (if there is a variable named "undefined" in
|
||||
scope; we do it because the variable name will be mangled, typically
|
||||
reduced to a single character).
|
||||
|
||||
### Conditional compilation
|
||||
|
||||
You can use the `--define` (`-d`) switch in order to declare global
|
||||
@@ -206,13 +241,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 +324,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 +346,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)
|
||||
@@ -324,6 +360,8 @@ Example:
|
||||
|
||||
var result = UglifyJS.minify("/path/to/file.js");
|
||||
console.log(result.code); // minified output
|
||||
// if you need to pass code instead of file name
|
||||
var result = UglifyJS.minify("var b = function () {};", {fromString: true});
|
||||
|
||||
You can also compress multiple files:
|
||||
|
||||
@@ -342,6 +380,14 @@ Note that the source map is not saved in a file, it's just returned in
|
||||
`result.map`. The value passed for `outSourceMap` is only used to set the
|
||||
`file` attribute in the source map (see [the spec][sm-spec]).
|
||||
|
||||
You can also specify sourceRoot property to be included in source map:
|
||||
|
||||
var result = UglifyJS.minify([ "file1.js", "file2.js", "file3.js" ], {
|
||||
outSourceMap: "out.js.map",
|
||||
sourceRoot: "http://example.com/src"
|
||||
});
|
||||
|
||||
|
||||
If you're compressing compiled JavaScript and have a source map for it, you
|
||||
can use the `inSourceMap` argument:
|
||||
|
||||
@@ -354,6 +400,22 @@ can use the `inSourceMap` argument:
|
||||
The `inSourceMap` is only used if you also request `outSourceMap` (it makes
|
||||
no sense otherwise).
|
||||
|
||||
Other options:
|
||||
|
||||
- `warnings` (default `false`) — pass `true` to display compressor warnings.
|
||||
|
||||
- `fromString` (default `false`) — if you pass `true` then you can pass
|
||||
JavaScript source code, rather than file names.
|
||||
|
||||
- `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!
|
||||
|
||||
@@ -501,3 +563,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
|
||||
|
||||
@@ -7,6 +7,7 @@ var UglifyJS = require("../tools/node");
|
||||
var sys = require("util");
|
||||
var optimist = require("optimist");
|
||||
var fs = require("fs");
|
||||
var async = require("async");
|
||||
var acorn;
|
||||
var ARGS = optimist
|
||||
.usage("$0 input1.js [input2.js ...] [options]\n\
|
||||
@@ -19,7 +20,9 @@ mangling you need to use `-c` and `-m`.\
|
||||
")
|
||||
.describe("source-map", "Specify an output file where to generate source map.")
|
||||
.describe("source-map-root", "The path to the original source to be included in the source map.")
|
||||
.describe("source-map-url", "The path to the source map to be added in //@ sourceMappingURL. Defaults to the value passed with --source-map.")
|
||||
.describe("in-source-map", "Input source map, useful if you're compressing JS that was generated from some other original code.")
|
||||
.describe("screw-ie8", "Pass this flag if you don't care about full compliance with Internet Explorer 6-8 quirks (by default UglifyJS will try to be IE-proof).")
|
||||
.describe("p", "Skip prefix for original filenames that appear in source maps. \
|
||||
For example -p 3 will drop 3 directories from file names and ensure they are relative paths.")
|
||||
.describe("o", "Output file (default STDOUT).")
|
||||
@@ -30,6 +33,7 @@ For example -p 3 will drop 3 directories from file names and ensure they are rel
|
||||
Pass options like -c hoist_vars=false,if_return=false. \
|
||||
Use -c with no argument to use the default compression options.")
|
||||
.describe("d", "Global definitions")
|
||||
.describe("e", "Embed everything in a big function, with a configurable parameter/argument list.")
|
||||
|
||||
.describe("comments", "Preserve copyright comments in the output. \
|
||||
By default this works like Google Closure, keeping JSDoc-style comments that contain \"@license\" or \"@preserve\". \
|
||||
@@ -49,6 +53,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,15 +63,20 @@ 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")
|
||||
.alias("e", "enclose")
|
||||
|
||||
.string("source-map")
|
||||
.string("source-map-root")
|
||||
.string("source-map-url")
|
||||
.string("b")
|
||||
.string("m")
|
||||
.string("c")
|
||||
.string("d")
|
||||
.string("e")
|
||||
.string("comments")
|
||||
.string("wrap")
|
||||
.boolean("screw-ie8")
|
||||
.boolean("export-all")
|
||||
.boolean("self")
|
||||
.boolean("v")
|
||||
@@ -74,6 +84,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 +93,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));
|
||||
@@ -101,12 +118,17 @@ var COMPRESS = getOptions("c", true);
|
||||
var MANGLE = getOptions("m", true);
|
||||
var BEAUTIFY = getOptions("b", true);
|
||||
|
||||
if (COMPRESS && ARGS.d) {
|
||||
COMPRESS.global_defs = getOptions("d");
|
||||
if (ARGS.d) {
|
||||
if (COMPRESS) COMPRESS.global_defs = getOptions("d");
|
||||
}
|
||||
|
||||
if (MANGLE && ARGS.r) {
|
||||
MANGLE.except = ARGS.r.replace(/^\s+|\s+$/g).split(/\s*,+\s*/);
|
||||
if (ARGS.screw_ie8) {
|
||||
if (COMPRESS) COMPRESS.screw_ie8 = true;
|
||||
if (MANGLE) MANGLE.screw_ie8 = true;
|
||||
}
|
||||
|
||||
if (ARGS.r) {
|
||||
if (MANGLE) MANGLE.except = ARGS.r.replace(/^\s+|\s+$/g).split(/\s*,+\s*/);
|
||||
}
|
||||
|
||||
var OUTPUT_OPTIONS = {
|
||||
@@ -127,7 +149,7 @@ if (ARGS.comments) {
|
||||
var type = comment.type;
|
||||
if (type == "comment2") {
|
||||
// multiline comment
|
||||
return /@preserve|@license|@cc_on/i.test(test);
|
||||
return /@preserve|@license|@cc_on/i.test(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -195,100 +217,116 @@ try {
|
||||
}
|
||||
}
|
||||
|
||||
files.forEach(function(file) {
|
||||
var code = read_whole_file(file);
|
||||
if (ARGS.p != null) {
|
||||
file = file.replace(/^\/+/, "").split(/\/+/).slice(ARGS.p).join("/");
|
||||
async.eachLimit(files, 1, function (file, cb) {
|
||||
read_whole_file(file, function (err, code) {
|
||||
if (err) {
|
||||
sys.error("ERROR: can't read file: " + filename);
|
||||
process.exit(1);
|
||||
}
|
||||
if (ARGS.p != null) {
|
||||
file = file.replace(/^\/+/, "").split(/\/+/).slice(ARGS.p).join("/");
|
||||
}
|
||||
time_it("parse", function(){
|
||||
if (ARGS.spidermonkey) {
|
||||
var program = JSON.parse(code);
|
||||
if (!TOPLEVEL) TOPLEVEL = program;
|
||||
else TOPLEVEL.body = TOPLEVEL.body.concat(program.body);
|
||||
}
|
||||
else if (ARGS.acorn) {
|
||||
TOPLEVEL = acorn.parse(code, {
|
||||
locations : true,
|
||||
trackComments : true,
|
||||
sourceFile : file,
|
||||
program : TOPLEVEL
|
||||
});
|
||||
}
|
||||
else {
|
||||
TOPLEVEL = UglifyJS.parse(code, {
|
||||
filename: file,
|
||||
toplevel: TOPLEVEL
|
||||
});
|
||||
};
|
||||
});
|
||||
cb();
|
||||
});
|
||||
}, function () {
|
||||
if (ARGS.acorn || ARGS.spidermonkey) time_it("convert_ast", function(){
|
||||
TOPLEVEL = UglifyJS.AST_Node.from_mozilla_ast(TOPLEVEL);
|
||||
});
|
||||
|
||||
if (ARGS.wrap) {
|
||||
TOPLEVEL = TOPLEVEL.wrap_commonjs(ARGS.wrap, ARGS.export_all);
|
||||
}
|
||||
time_it("parse", function(){
|
||||
if (ARGS.spidermonkey) {
|
||||
var program = JSON.parse(code);
|
||||
if (!TOPLEVEL) TOPLEVEL = program;
|
||||
else TOPLEVEL.body = TOPLEVEL.body.concat(program.body);
|
||||
|
||||
if (ARGS.enclose) {
|
||||
var arg_parameter_list = ARGS.enclose;
|
||||
|
||||
if (!(arg_parameter_list instanceof Array)) {
|
||||
arg_parameter_list = [arg_parameter_list];
|
||||
}
|
||||
else if (ARGS.acorn) {
|
||||
TOPLEVEL = acorn.parse(code, {
|
||||
locations : true,
|
||||
trackComments : true,
|
||||
sourceFile : file,
|
||||
program : TOPLEVEL
|
||||
});
|
||||
}
|
||||
else {
|
||||
TOPLEVEL = UglifyJS.parse(code, {
|
||||
filename: file,
|
||||
toplevel: TOPLEVEL
|
||||
});
|
||||
};
|
||||
|
||||
TOPLEVEL = TOPLEVEL.wrap_enclose(arg_parameter_list);
|
||||
}
|
||||
|
||||
var SCOPE_IS_NEEDED = COMPRESS || MANGLE || ARGS.lint;
|
||||
|
||||
if (SCOPE_IS_NEEDED) {
|
||||
time_it("scope", function(){
|
||||
TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8 });
|
||||
if (ARGS.lint) {
|
||||
TOPLEVEL.scope_warnings();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (COMPRESS) {
|
||||
time_it("squeeze", function(){
|
||||
TOPLEVEL = TOPLEVEL.transform(compressor);
|
||||
});
|
||||
}
|
||||
|
||||
if (SCOPE_IS_NEEDED) {
|
||||
time_it("scope", function(){
|
||||
TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8 });
|
||||
if (MANGLE) {
|
||||
TOPLEVEL.compute_char_frequency(MANGLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (MANGLE) time_it("mangle", function(){
|
||||
TOPLEVEL.mangle_names(MANGLE);
|
||||
});
|
||||
});
|
||||
|
||||
if (ARGS.acorn || ARGS.spidermonkey) time_it("convert_ast", function(){
|
||||
TOPLEVEL = UglifyJS.AST_Node.from_mozilla_ast(TOPLEVEL);
|
||||
});
|
||||
|
||||
if (ARGS.wrap) {
|
||||
TOPLEVEL = TOPLEVEL.wrap_commonjs(ARGS.wrap, ARGS.export_all);
|
||||
}
|
||||
|
||||
var SCOPE_IS_NEEDED = COMPRESS || MANGLE || ARGS.lint;
|
||||
|
||||
if (SCOPE_IS_NEEDED) {
|
||||
time_it("scope", function(){
|
||||
TOPLEVEL.figure_out_scope();
|
||||
if (ARGS.lint) {
|
||||
TOPLEVEL.scope_warnings();
|
||||
}
|
||||
time_it("generate", function(){
|
||||
TOPLEVEL.print(output);
|
||||
});
|
||||
}
|
||||
|
||||
if (COMPRESS) {
|
||||
time_it("squeeze", function(){
|
||||
TOPLEVEL = TOPLEVEL.transform(compressor);
|
||||
});
|
||||
}
|
||||
output = output.get();
|
||||
|
||||
if (SCOPE_IS_NEEDED) {
|
||||
time_it("scope", function(){
|
||||
TOPLEVEL.figure_out_scope();
|
||||
if (MANGLE) {
|
||||
TOPLEVEL.compute_char_frequency();
|
||||
}
|
||||
});
|
||||
}
|
||||
if (SOURCE_MAP) {
|
||||
fs.writeFileSync(ARGS.source_map, SOURCE_MAP, "utf8");
|
||||
output += "\n/*\n//@ sourceMappingURL=" + (ARGS.source_map_url || ARGS.source_map) + "\n*/";
|
||||
}
|
||||
|
||||
if (MANGLE) time_it("mangle", function(){
|
||||
TOPLEVEL.mangle_names(MANGLE);
|
||||
});
|
||||
time_it("generate", function(){
|
||||
TOPLEVEL.print(output);
|
||||
});
|
||||
if (OUTPUT_FILE) {
|
||||
fs.writeFileSync(OUTPUT_FILE, output, "utf8");
|
||||
} else {
|
||||
sys.print(output);
|
||||
sys.error("\n");
|
||||
}
|
||||
|
||||
output = output.get();
|
||||
|
||||
if (SOURCE_MAP) {
|
||||
fs.writeFileSync(ARGS.source_map, SOURCE_MAP, "utf8");
|
||||
output += "\n//@ sourceMappingURL=" + ARGS.source_map;
|
||||
}
|
||||
|
||||
if (OUTPUT_FILE) {
|
||||
fs.writeFileSync(OUTPUT_FILE, output, "utf8");
|
||||
} else {
|
||||
sys.print(output);
|
||||
sys.error("\n");
|
||||
}
|
||||
|
||||
if (ARGS.stats) {
|
||||
sys.error(UglifyJS.string_template("Timing information (compressed {count} files):", {
|
||||
count: files.length
|
||||
}));
|
||||
for (var i in STATS) if (STATS.hasOwnProperty(i)) {
|
||||
sys.error(UglifyJS.string_template("- {name}: {time}s", {
|
||||
name: i,
|
||||
time: (STATS[i] / 1000).toFixed(3)
|
||||
if (ARGS.stats) {
|
||||
sys.error(UglifyJS.string_template("Timing information (compressed {count} files):", {
|
||||
count: files.length
|
||||
}));
|
||||
for (var i in STATS) if (STATS.hasOwnProperty(i)) {
|
||||
sys.error(UglifyJS.string_template("- {name}: {time}s", {
|
||||
name: i,
|
||||
time: (STATS[i] / 1000).toFixed(3)
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* -----[ functions ]----- */
|
||||
|
||||
@@ -333,17 +371,18 @@ function getOptions(x, constants) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
function read_whole_file(filename) {
|
||||
function read_whole_file(filename, cb) {
|
||||
if (filename == "-") {
|
||||
// XXX: this sucks. How does one read the whole STDIN
|
||||
// synchronously?
|
||||
filename = "/dev/stdin";
|
||||
}
|
||||
try {
|
||||
return fs.readFileSync(filename, "utf8");
|
||||
} catch(ex) {
|
||||
sys.error("ERROR: can't read file: " + filename);
|
||||
process.exit(1);
|
||||
var chunks = [];
|
||||
process.stdin.setEncoding('utf-8');
|
||||
process.stdin.on('data', function (chunk) {
|
||||
chunks.push(chunk);
|
||||
}).on('end', function () {
|
||||
cb(null, chunks.join(""));
|
||||
});
|
||||
process.openStdin();
|
||||
} else {
|
||||
fs.readFile(filename, "utf-8", cb);
|
||||
}
|
||||
}
|
||||
|
||||
58
lib/ast.js
58
lib/ast.js
@@ -285,11 +285,32 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", {
|
||||
$propdoc: {
|
||||
globals: "[Object/S] a map of name -> SymbolDef for all undeclared names",
|
||||
},
|
||||
wrap_enclose: function(arg_parameter_pairs) {
|
||||
var self = this;
|
||||
var args = [];
|
||||
var parameters = [];
|
||||
|
||||
arg_parameter_pairs.forEach(function(pair) {
|
||||
var split = pair.split(":");
|
||||
|
||||
args.push(split[0]);
|
||||
parameters.push(split[1]);
|
||||
});
|
||||
|
||||
var wrapped_tl = "(function(" + parameters.join(",") + "){ '$ORIG'; })(" + args.join(",") + ")";
|
||||
wrapped_tl = parse(wrapped_tl);
|
||||
wrapped_tl = wrapped_tl.transform(new TreeTransformer(function before(node){
|
||||
if (node instanceof AST_Directive && node.value == "$ORIG") {
|
||||
return MAP.splice(self.body);
|
||||
}
|
||||
}));
|
||||
return wrapped_tl;
|
||||
},
|
||||
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 +366,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);
|
||||
@@ -581,6 +606,18 @@ var AST_Seq = DEFNODE("Seq", "car cdr", {
|
||||
}
|
||||
return list;
|
||||
},
|
||||
to_array: function() {
|
||||
var p = this, a = [];
|
||||
while (p) {
|
||||
a.push(p.car);
|
||||
if (p.cdr && !(p.cdr instanceof AST_Seq)) {
|
||||
a.push(p.cdr);
|
||||
break;
|
||||
}
|
||||
p = p.cdr;
|
||||
}
|
||||
return a;
|
||||
},
|
||||
add: function(node) {
|
||||
var p = this;
|
||||
while (p) {
|
||||
@@ -746,6 +783,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: {
|
||||
@@ -790,7 +831,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",
|
||||
@@ -843,6 +884,11 @@ var AST_Undefined = DEFNODE("Undefined", null, {
|
||||
value: (function(){}())
|
||||
}, AST_Atom);
|
||||
|
||||
var AST_Hole = DEFNODE("Hole", null, {
|
||||
$documentation: "A hole in an array",
|
||||
value: (function(){}())
|
||||
}, AST_Atom);
|
||||
|
||||
var AST_Infinity = DEFNODE("Infinity", null, {
|
||||
$documentation: "The `Infinity` value",
|
||||
value: 1/0
|
||||
@@ -929,10 +975,10 @@ TreeWalker.prototype = {
|
||||
} else {
|
||||
for (var i = stack.length; --i >= 0;) {
|
||||
var x = stack[i];
|
||||
if (x instanceof AST_Switch) return x;
|
||||
if (x instanceof AST_For || x instanceof AST_ForIn || x instanceof AST_DWLoop) {
|
||||
return (x.body instanceof AST_BlockStatement ? x.body : x);
|
||||
}
|
||||
if (x instanceof AST_Switch
|
||||
|| x instanceof AST_For
|
||||
|| x instanceof AST_ForIn
|
||||
|| x instanceof AST_DWLoop) return x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
528
lib/compress.js
528
lib/compress.js
@@ -52,7 +52,8 @@ function Compressor(options, false_by_default) {
|
||||
properties : !false_by_default,
|
||||
dead_code : !false_by_default,
|
||||
drop_debugger : !false_by_default,
|
||||
unsafe : !false_by_default,
|
||||
unsafe : false,
|
||||
unsafe_comps : false,
|
||||
conditionals : !false_by_default,
|
||||
comparisons : !false_by_default,
|
||||
evaluate : !false_by_default,
|
||||
@@ -65,6 +66,7 @@ function Compressor(options, false_by_default) {
|
||||
join_vars : !false_by_default,
|
||||
cascade : !false_by_default,
|
||||
side_effects : !false_by_default,
|
||||
screw_ie8 : false,
|
||||
|
||||
warnings : true,
|
||||
global_defs : {}
|
||||
@@ -155,7 +157,7 @@ merge(Compressor.prototype, {
|
||||
value: val
|
||||
}).optimize(compressor);
|
||||
case "boolean":
|
||||
return make_node(val ? AST_True : AST_False, orig);
|
||||
return make_node(val ? AST_True : AST_False, orig).optimize(compressor);
|
||||
case "undefined":
|
||||
return make_node(AST_Undefined, orig).optimize(compressor);
|
||||
default:
|
||||
@@ -186,6 +188,14 @@ merge(Compressor.prototype, {
|
||||
return false;
|
||||
};
|
||||
|
||||
function loop_body(x) {
|
||||
if (x instanceof AST_Switch) return x;
|
||||
if (x instanceof AST_For || x instanceof AST_ForIn || x instanceof AST_DWLoop) {
|
||||
return (x.body instanceof AST_BlockStatement ? x.body : x);
|
||||
}
|
||||
return x;
|
||||
};
|
||||
|
||||
function tighten_body(statements, compressor) {
|
||||
var CHANGED;
|
||||
do {
|
||||
@@ -303,8 +313,13 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
|
||||
var ab = aborts(stat.body);
|
||||
var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab.label) : null;
|
||||
if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda)
|
||||
|| (ab instanceof AST_Continue && self === compressor.loopcontrol_target(ab.label)))) {
|
||||
|| (ab instanceof AST_Continue && self === loop_body(lct))
|
||||
|| (ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct))) {
|
||||
if (ab.label) {
|
||||
remove(ab.label.thedef.references, ab.label);
|
||||
}
|
||||
CHANGED = true;
|
||||
var body = as_statement_array(stat.body).slice(0, -1);
|
||||
stat = stat.clone();
|
||||
@@ -320,8 +335,13 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
|
||||
var ab = aborts(stat.alternative);
|
||||
var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab.label) : null;
|
||||
if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda)
|
||||
|| (ab instanceof AST_Continue && self === compressor.loopcontrol_target(ab.label)))) {
|
||||
|| (ab instanceof AST_Continue && self === loop_body(lct))
|
||||
|| (ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct))) {
|
||||
if (ab.label) {
|
||||
remove(ab.label.thedef.references, ab.label);
|
||||
}
|
||||
CHANGED = true;
|
||||
stat = stat.clone();
|
||||
stat.body = make_node(AST_BlockStatement, stat.body, {
|
||||
@@ -347,11 +367,26 @@ merge(Compressor.prototype, {
|
||||
function eliminate_dead_code(statements, compressor) {
|
||||
var has_quit = false;
|
||||
var orig = statements.length;
|
||||
var self = compressor.self();
|
||||
statements = statements.reduce(function(a, stat){
|
||||
if (has_quit) {
|
||||
extract_declarations_from_unreachable_code(compressor, stat, a);
|
||||
} else {
|
||||
a.push(stat);
|
||||
if (stat instanceof AST_LoopControl) {
|
||||
var lct = compressor.loopcontrol_target(stat.label);
|
||||
if ((stat instanceof AST_Break
|
||||
&& lct instanceof AST_BlockStatement
|
||||
&& loop_body(lct) === self) || (stat instanceof AST_Continue
|
||||
&& loop_body(lct) === self)) {
|
||||
if (stat.label) {
|
||||
remove(stat.label.thedef.references, stat.label);
|
||||
}
|
||||
} else {
|
||||
a.push(stat);
|
||||
}
|
||||
} else {
|
||||
a.push(stat);
|
||||
}
|
||||
if (aborts(stat)) has_quit = true;
|
||||
}
|
||||
return a;
|
||||
@@ -394,12 +429,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);
|
||||
@@ -509,12 +555,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);
|
||||
@@ -568,10 +626,24 @@ merge(Compressor.prototype, {
|
||||
var e = this.expression;
|
||||
switch (this.operator) {
|
||||
case "!": return !ev(e);
|
||||
case "typeof": return typeof ev(e);
|
||||
case "typeof":
|
||||
// Function would be evaluated to an array and so typeof would
|
||||
// incorrectly return 'object'. Hence making is a special case.
|
||||
if (e instanceof AST_Function) return typeof function(){};
|
||||
|
||||
e = ev(e);
|
||||
|
||||
// typeof <RegExp> returns "object" or "function" on different platforms
|
||||
// so cannot evaluate reliably
|
||||
if (e instanceof RegExp) throw def;
|
||||
|
||||
return typeof e;
|
||||
case "void": return void ev(e);
|
||||
case "~": return ~ev(e);
|
||||
case "-": return -ev(e);
|
||||
case "-":
|
||||
e = ev(e);
|
||||
if (e === 0) throw def;
|
||||
return -e;
|
||||
case "+": return +ev(e);
|
||||
}
|
||||
throw def;
|
||||
@@ -612,12 +684,7 @@ merge(Compressor.prototype, {
|
||||
});
|
||||
def(AST_SymbolRef, function(){
|
||||
var d = this.definition();
|
||||
if (d && d.constant) {
|
||||
var orig = d.orig[0];
|
||||
if (orig) orig = orig.init[0];
|
||||
orig = orig && orig.value;
|
||||
if (orig) return ev(orig);
|
||||
}
|
||||
if (d && d.constant && d.init) return ev(d.init);
|
||||
throw def;
|
||||
});
|
||||
})(function(node, func){
|
||||
@@ -659,7 +726,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;
|
||||
@@ -708,9 +775,10 @@ merge(Compressor.prototype, {
|
||||
});
|
||||
|
||||
def(AST_SimpleStatement, function(){
|
||||
if (this.body instanceof AST_Function) return false;
|
||||
return this.body.has_side_effects();
|
||||
});
|
||||
def(AST_Defun, function(){ return true });
|
||||
def(AST_Function, function(){ return false });
|
||||
def(AST_Binary, function(){
|
||||
return this.left.has_side_effects()
|
||||
|| this.right.has_side_effects();
|
||||
@@ -768,10 +836,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);
|
||||
});
|
||||
@@ -795,6 +865,10 @@ merge(Compressor.prototype, {
|
||||
});
|
||||
|
||||
OPT(AST_LabeledStatement, function(self, compressor){
|
||||
if (self.body instanceof AST_Break
|
||||
&& compressor.loopcontrol_target(self.body.label) === self.body) {
|
||||
return make_node(AST_EmptyStatement, self);
|
||||
}
|
||||
return self.label.references.length == 0 ? self.body : self;
|
||||
});
|
||||
|
||||
@@ -819,23 +893,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;
|
||||
}
|
||||
@@ -855,22 +934,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];
|
||||
@@ -961,6 +1038,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;
|
||||
}
|
||||
@@ -976,7 +1066,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){
|
||||
@@ -1001,7 +1091,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();
|
||||
@@ -1024,13 +1114,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;
|
||||
@@ -1061,13 +1209,66 @@ merge(Compressor.prototype, {
|
||||
extract_declarations_from_unreachable_code(compressor, self.body, a);
|
||||
return make_node(AST_BlockStatement, self, { body: a });
|
||||
}
|
||||
} else {
|
||||
return self.body;
|
||||
}
|
||||
}
|
||||
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) {
|
||||
@@ -1092,6 +1293,7 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
}
|
||||
}
|
||||
if_break_in_loop(self, compressor);
|
||||
return self;
|
||||
});
|
||||
|
||||
@@ -1182,8 +1384,8 @@ merge(Compressor.prototype, {
|
||||
return make_node(self.body.CTOR, self, {
|
||||
value: make_node(AST_Conditional, self, {
|
||||
condition : self.condition,
|
||||
consequent : self.body.value,
|
||||
alternative : self.alternative.value || make_node(AST_Undefined, self).optimize(compressor)
|
||||
consequent : self.body.value || make_node(AST_Undefined, self.body).optimize(compressor),
|
||||
alternative : self.alternative.value || make_node(AST_Undefined, self.alternative).optimize(compressor)
|
||||
})
|
||||
}).transform(compressor);
|
||||
}
|
||||
@@ -1224,11 +1426,91 @@ merge(Compressor.prototype, {
|
||||
body: self.expression
|
||||
}).transform(compressor);
|
||||
}
|
||||
var last_branch = self.body[self.body.length - 1];
|
||||
if (last_branch) {
|
||||
var stat = last_branch.body[last_branch.body.length - 1]; // last statement
|
||||
if (stat instanceof AST_Break && compressor.loopcontrol_target(stat.label) === self)
|
||||
last_branch.body.pop();
|
||||
for(;;) {
|
||||
var last_branch = self.body[self.body.length - 1];
|
||||
if (last_branch) {
|
||||
var stat = last_branch.body[last_branch.body.length - 1]; // last statement
|
||||
if (stat instanceof AST_Break && loop_body(compressor.loopcontrol_target(stat.label)) === self)
|
||||
last_branch.body.pop();
|
||||
if (last_branch instanceof AST_Default && last_branch.body.length == 0) {
|
||||
self.body.pop();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
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;
|
||||
});
|
||||
@@ -1299,6 +1581,9 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
break;
|
||||
case "String":
|
||||
if (self.args.length == 0) return make_node(AST_String, self, {
|
||||
value: ""
|
||||
});
|
||||
return make_node(AST_Binary, self, {
|
||||
left: self.args[0],
|
||||
operator: "+",
|
||||
@@ -1308,16 +1593,16 @@ 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);
|
||||
}
|
||||
}
|
||||
if (compressor.option("side_effects")) {
|
||||
if (self.expression instanceof AST_Function
|
||||
&& self.args.length == 0
|
||||
&& !self.expression.has_side_effects()) {
|
||||
&& !AST_Block.prototype.has_side_effects.call(self.expression)) {
|
||||
return make_node(AST_Undefined, self).transform(compressor);
|
||||
}
|
||||
}
|
||||
@@ -1334,7 +1619,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1342,6 +1627,21 @@ merge(Compressor.prototype, {
|
||||
});
|
||||
|
||||
OPT(AST_Seq, function(self, compressor){
|
||||
if (!compressor.option("side_effects"))
|
||||
return self;
|
||||
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()
|
||||
@@ -1357,7 +1657,26 @@ merge(Compressor.prototype, {
|
||||
return self;
|
||||
});
|
||||
|
||||
AST_Unary.DEFMETHOD("lift_sequences", function(compressor){
|
||||
if (compressor.option("sequences")) {
|
||||
if (this.expression instanceof AST_Seq) {
|
||||
var seq = this.expression;
|
||||
var x = seq.to_array();
|
||||
this.expression = x.pop();
|
||||
x.push(this);
|
||||
seq = AST_Seq.from_array(x).transform(compressor);
|
||||
return seq;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
});
|
||||
|
||||
OPT(AST_UnaryPostfix, function(self, compressor){
|
||||
return self.lift_sequences(compressor);
|
||||
});
|
||||
|
||||
OPT(AST_UnaryPrefix, function(self, compressor){
|
||||
self = self.lift_sequences(compressor);
|
||||
var e = self.expression;
|
||||
if (compressor.option("booleans") && compressor.in_boolean_context()) {
|
||||
switch (self.operator) {
|
||||
@@ -1373,39 +1692,70 @@ 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];
|
||||
});
|
||||
|
||||
AST_Binary.DEFMETHOD("lift_sequences", function(compressor){
|
||||
if (compressor.option("sequences")) {
|
||||
if (this.left instanceof AST_Seq) {
|
||||
var seq = this.left;
|
||||
var x = seq.to_array();
|
||||
this.left = x.pop();
|
||||
x.push(this);
|
||||
seq = AST_Seq.from_array(x).transform(compressor);
|
||||
return seq;
|
||||
}
|
||||
if (this.right instanceof AST_Seq
|
||||
&& !(this.operator == "||" || this.operator == "&&")
|
||||
&& !this.left.has_side_effects()) {
|
||||
var seq = this.right;
|
||||
var x = seq.to_array();
|
||||
this.right = x.pop();
|
||||
x.push(this);
|
||||
seq = AST_Seq.from_array(x).transform(compressor);
|
||||
return seq;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
});
|
||||
|
||||
var commutativeOperators = makePredicate("== === != !== * & | ^");
|
||||
|
||||
OPT(AST_Binary, function(self, compressor){
|
||||
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"
|
||||
&& compressor.option("unsafe")) {
|
||||
if (!(self.right.expression instanceof AST_SymbolRef)
|
||||
|| !self.right.expression.undeclared()) {
|
||||
self.left = self.right.expression;
|
||||
@@ -1468,17 +1818,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;
|
||||
});
|
||||
|
||||
@@ -1519,6 +1868,7 @@ merge(Compressor.prototype, {
|
||||
|
||||
var ASSIGN_OPS = [ '+', '-', '/', '*', '%', '>>', '<<', '>>>', '|', '^', '&' ];
|
||||
OPT(AST_Assign, function(self, compressor){
|
||||
self = self.lift_sequences(compressor);
|
||||
if (self.operator == "="
|
||||
&& self.left instanceof AST_SymbolRef
|
||||
&& self.right instanceof AST_Binary
|
||||
@@ -1612,7 +1962,7 @@ merge(Compressor.prototype, {
|
||||
var prop = self.property;
|
||||
if (prop instanceof AST_String && compressor.option("properties")) {
|
||||
prop = prop.getValue();
|
||||
if (is_identifier(prop)) {
|
||||
if (is_identifier(prop) || compressor.option("screw_ie8")) {
|
||||
return make_node(AST_Dot, self, {
|
||||
expression : self.expression,
|
||||
property : prop
|
||||
|
||||
@@ -148,12 +148,14 @@
|
||||
};
|
||||
|
||||
function From_Moz_Unary(M) {
|
||||
return new (M.prefix ? AST_UnaryPrefix : AST_UnaryPostfix)({
|
||||
var prefix = "prefix" in M ? M.prefix
|
||||
: M.type == "UnaryExpression" ? true : false;
|
||||
return new (prefix ? AST_UnaryPrefix : AST_UnaryPostfix)({
|
||||
start : my_start_token(M),
|
||||
end : my_end_token(M),
|
||||
operator : M.operator,
|
||||
expression : from_moz(M.argument)
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
var ME_TO_MOZ = {};
|
||||
|
||||
170
lib/output.js
170
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;
|
||||
@@ -68,11 +69,16 @@ function OutputStream(options) {
|
||||
var current_pos = 0;
|
||||
var OUTPUT = "";
|
||||
|
||||
function to_ascii(str) {
|
||||
function to_ascii(str, identifier) {
|
||||
return str.replace(/[\u0080-\uffff]/g, function(ch) {
|
||||
var code = ch.charCodeAt(0).toString(16);
|
||||
while (code.length < 4) code = "0" + code;
|
||||
return "\\u" + code;
|
||||
if (code.length <= 2 && !identifier) {
|
||||
while (code.length < 2) code = "0" + code;
|
||||
return "\\x" + code;
|
||||
} else {
|
||||
while (code.length < 4) code = "0" + code;
|
||||
return "\\u" + code;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -108,7 +114,7 @@ function OutputStream(options) {
|
||||
function make_name(name) {
|
||||
name = name.toString();
|
||||
if (options.ascii_only)
|
||||
name = to_ascii(name);
|
||||
name = to_ascii(name, true);
|
||||
return name;
|
||||
};
|
||||
|
||||
@@ -137,7 +143,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 +160,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 +345,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);
|
||||
@@ -360,6 +380,16 @@ function OutputStream(options) {
|
||||
if (start && !start._comments_dumped) {
|
||||
start._comments_dumped = true;
|
||||
var comments = start.comments_before;
|
||||
|
||||
// XXX: ugly fix for https://github.com/mishoo/UglifyJS2/issues/112
|
||||
// if this node is `return` or `throw`, we cannot allow comments before
|
||||
// the returned or thrown value.
|
||||
if (self instanceof AST_Exit &&
|
||||
self.value && self.value.start.comments_before.length > 0) {
|
||||
comments = (comments || []).concat(self.value.start.comments_before);
|
||||
self.value.start.comments_before = [];
|
||||
}
|
||||
|
||||
if (c.test) {
|
||||
comments = comments.filter(function(comment){
|
||||
return c.test(comment.value);
|
||||
@@ -410,11 +440,16 @@ 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)
|
||||
|| p instanceof AST_Unary // !(foo, bar, baz)
|
||||
|| p instanceof AST_Binary // 1 + (2, 3) + 4 ==> 7
|
||||
|| p instanceof AST_Binary // 1 + (2, 3) + 4 ==> 8
|
||||
|| p instanceof AST_VarDef // var a = (1, 2), b = a + a; ==> b == 4
|
||||
|| p instanceof AST_Dot // (1, {foo:2}).foo ==> 2
|
||||
|| p instanceof AST_Array // [ 1, (2, 3), 4 ] ==> [ 1, 3, 4 ]
|
||||
@@ -449,24 +484,50 @@ 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_PropAccess // (new Date).getTime(), (new Date)["getTime"]()
|
||||
|| p instanceof AST_Call && p.expression === this)) // (new foo)(bar)
|
||||
return true;
|
||||
});
|
||||
|
||||
PARENS(AST_Number, function(output){
|
||||
var p = output.parent();
|
||||
if (this.getValue() < 0 && p instanceof AST_PropAccess && p.expression === this)
|
||||
return true;
|
||||
});
|
||||
|
||||
PARENS(AST_NaN, function(output){
|
||||
var p = output.parent();
|
||||
if (p instanceof AST_PropAccess && p.expression === this)
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -529,6 +590,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 +639,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 +755,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 +779,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 +887,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 +931,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){
|
||||
@@ -874,7 +959,7 @@ function OutputStream(options) {
|
||||
DEFPRINT(AST_Dot, function(self, output){
|
||||
var expr = self.expression;
|
||||
expr.print(output);
|
||||
if (expr instanceof AST_Number) {
|
||||
if (expr instanceof AST_Number && expr.getValue() >= 0) {
|
||||
if (!/[xa-f.]/i.test(output.last())) {
|
||||
output.print(".");
|
||||
}
|
||||
@@ -926,8 +1011,7 @@ function OutputStream(options) {
|
||||
if (len > 0) output.space();
|
||||
a.forEach(function(exp, i){
|
||||
if (i) output.comma();
|
||||
if (!(exp instanceof AST_Undefined))
|
||||
exp.print(output);
|
||||
exp.print(output);
|
||||
});
|
||||
if (len > 0) output.space();
|
||||
});
|
||||
@@ -949,7 +1033,7 @@ function OutputStream(options) {
|
||||
DEFPRINT(AST_ObjectKeyVal, function(self, output){
|
||||
var key = self.key;
|
||||
if (output.option("quote_keys")) {
|
||||
output.print_string(key);
|
||||
output.print_string(key + "");
|
||||
} else if ((typeof key == "number"
|
||||
|| !output.option("beautify")
|
||||
&& +key + "" == key)
|
||||
@@ -978,6 +1062,7 @@ function OutputStream(options) {
|
||||
DEFPRINT(AST_Undefined, function(self, output){
|
||||
output.print("void 0");
|
||||
});
|
||||
DEFPRINT(AST_Hole, noop);
|
||||
DEFPRINT(AST_Infinity, function(self, output){
|
||||
output.print("1/0");
|
||||
});
|
||||
@@ -1001,6 +1086,9 @@ function OutputStream(options) {
|
||||
if (output.option("ascii_only"))
|
||||
str = output.to_ascii(str);
|
||||
output.print(str);
|
||||
var p = output.parent();
|
||||
if (p instanceof AST_Binary && /^in/.test(p.operator) && p.left === self)
|
||||
output.print(" ");
|
||||
});
|
||||
|
||||
function force_statement(stat, output) {
|
||||
|
||||
27
lib/parse.js
27
lib/parse.js
@@ -149,7 +149,7 @@ function is_unicode_connector_punctuation(ch) {
|
||||
};
|
||||
|
||||
function is_identifier(name) {
|
||||
return /^[a-z_$][a-z0-9_$]*$/i.test(name) && !RESERVED_WORDS(name);
|
||||
return !RESERVED_WORDS(name) && /^[a-z_$][a-z0-9_$]*$/i.test(name);
|
||||
};
|
||||
|
||||
function is_identifier_start(code) {
|
||||
@@ -190,12 +190,6 @@ JS_Parse_Error.prototype.toString = function() {
|
||||
};
|
||||
|
||||
function js_error(message, filename, line, col, pos) {
|
||||
AST_Node.warn("ERROR: {message} [{file}:{line},{col}]", {
|
||||
message: message,
|
||||
file: filename,
|
||||
line: line,
|
||||
col: col
|
||||
});
|
||||
throw new JS_Parse_Error(message, line, col, pos);
|
||||
};
|
||||
|
||||
@@ -881,9 +875,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("(");
|
||||
@@ -1126,7 +1125,7 @@ function parse($TEXT, options) {
|
||||
if (first) first = false; else expect(",");
|
||||
if (allow_trailing_comma && is("punc", closing)) break;
|
||||
if (is("punc", ",") && allow_empty) {
|
||||
a.push(new AST_Undefined({ start: S.token, end: S.token }));
|
||||
a.push(new AST_Hole({ start: S.token, end: S.token }));
|
||||
} else {
|
||||
a.push(expression(false));
|
||||
}
|
||||
@@ -1158,7 +1157,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 +1166,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;
|
||||
@@ -1353,7 +1352,7 @@ function parse($TEXT, options) {
|
||||
left : left,
|
||||
operator : val,
|
||||
right : maybe_assign(no_in),
|
||||
end : peek()
|
||||
end : prev()
|
||||
});
|
||||
}
|
||||
croak("Invalid assignment");
|
||||
|
||||
136
lib/scope.js
136
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,22 @@ 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 && !(options && options.toplevel))
|
||||
|| 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)) {
|
||||
var s = this.scope;
|
||||
if (this.orig[0] instanceof AST_SymbolLambda && !options.screw_ie8)
|
||||
s = s.parent_scope;
|
||||
this.mangled_name = s.next_mangled(options);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -75,14 +82,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 +110,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;
|
||||
}
|
||||
@@ -116,7 +126,6 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
|
||||
}
|
||||
if (node instanceof AST_SymbolLambda) {
|
||||
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
|
||||
@@ -125,14 +134,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);
|
||||
def.init = tw.parent().value;
|
||||
}
|
||||
else if (node instanceof AST_SymbolCatch) {
|
||||
// XXX: this is wrong according to ECMA-262 (12.4). the
|
||||
@@ -144,7 +151,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 +164,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 +182,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 +207,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 +224,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 +237,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 +250,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,33 +260,33 @@ 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(){
|
||||
var ext = this.enclosed, n = ext.length;
|
||||
AST_Scope.DEFMETHOD("next_mangled", function(options){
|
||||
var ext = this.enclosed;
|
||||
out: while (true) {
|
||||
var m = base54(++this.cname);
|
||||
if (!is_identifier(m)) continue; // skip over "do"
|
||||
// we must ensure that the mangled name does not shadow a name
|
||||
// from some parent scope that is referenced in this or in
|
||||
// inner scopes.
|
||||
for (var i = n; --i >= 0;) {
|
||||
for (var i = ext.length; --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 +298,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 +337,18 @@ 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,
|
||||
toplevel : false,
|
||||
screw_ie8 : 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;
|
||||
|
||||
48
lib/utils.js
48
lib/utils.js
@@ -166,6 +166,12 @@ function string_template(text, props) {
|
||||
});
|
||||
};
|
||||
|
||||
function remove(array, el) {
|
||||
for (var i = array.length; --i >= 0;) {
|
||||
if (array[i] === el) array.splice(i, 1);
|
||||
}
|
||||
};
|
||||
|
||||
function mergeSort(array, cmp) {
|
||||
if (array.length < 2) return array.slice();
|
||||
function merge(a, b) {
|
||||
@@ -238,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;
|
||||
}
|
||||
};
|
||||
|
||||
11
package.json
11
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.0.0",
|
||||
"version": "2.3.0",
|
||||
"engines": { "node" : ">=0.4.0" },
|
||||
"maintainers": [{
|
||||
"name": "Mihai Bazon",
|
||||
@@ -15,11 +15,12 @@
|
||||
"url": "https://github.com/mishoo/UglifyJS2.git"
|
||||
}],
|
||||
"dependencies": {
|
||||
"source-map" : "*",
|
||||
"optimist" : "*"
|
||||
"async" : "~0.2.6",
|
||||
"source-map" : "~0.1.7",
|
||||
"optimist" : "~0.3.5"
|
||||
},
|
||||
"bin": {
|
||||
"uglifyjs2" : "bin/uglifyjs2"
|
||||
"uglifyjs" : "bin/uglifyjs"
|
||||
},
|
||||
"scripts": {"test": "node test/run-tests.js"}
|
||||
}
|
||||
|
||||
12
test/compress/arrays.js
Normal file
12
test/compress/arrays.js
Normal file
@@ -0,0 +1,12 @@
|
||||
holes_and_undefined: {
|
||||
input: {
|
||||
x = [1, 2, undefined];
|
||||
y = [1, , 2, ];
|
||||
z = [1, undefined, 3];
|
||||
}
|
||||
expect: {
|
||||
x=[1,2,void 0];
|
||||
y=[1,,2];
|
||||
z=[1,void 0,3];
|
||||
}
|
||||
}
|
||||
17
test/compress/issue-105.js
Normal file
17
test/compress/issue-105.js
Normal file
@@ -0,0 +1,17 @@
|
||||
typeof_eq_undefined: {
|
||||
options = {
|
||||
comparisons: true,
|
||||
unsafe: false
|
||||
};
|
||||
input: { a = typeof b.c != "undefined" }
|
||||
expect: { a = "undefined" != typeof b.c }
|
||||
}
|
||||
|
||||
typeof_eq_undefined_unsafe: {
|
||||
options = {
|
||||
comparisons: true,
|
||||
unsafe: true
|
||||
};
|
||||
input: { a = typeof b.c != "undefined" }
|
||||
expect: { a = b.c !== void 0 }
|
||||
}
|
||||
17
test/compress/issue-22.js
Normal file
17
test/compress/issue-22.js
Normal file
@@ -0,0 +1,17 @@
|
||||
return_with_no_value_in_if_body: {
|
||||
options = { conditionals: true };
|
||||
input: {
|
||||
function foo(bar) {
|
||||
if (bar) {
|
||||
return;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
function foo (bar) {
|
||||
return bar ? void 0 : 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
163
test/compress/labels.js
Normal file
163
test/compress/labels.js
Normal file
@@ -0,0 +1,163 @@
|
||||
labels_1: {
|
||||
options = { if_return: true, conditionals: true, dead_code: true };
|
||||
input: {
|
||||
out: {
|
||||
if (foo) break out;
|
||||
console.log("bar");
|
||||
}
|
||||
};
|
||||
expect: {
|
||||
foo || console.log("bar");
|
||||
}
|
||||
}
|
||||
|
||||
labels_2: {
|
||||
options = { if_return: true, conditionals: true, dead_code: true };
|
||||
input: {
|
||||
out: {
|
||||
if (foo) print("stuff");
|
||||
else break out;
|
||||
console.log("here");
|
||||
}
|
||||
};
|
||||
expect: {
|
||||
if (foo) {
|
||||
print("stuff");
|
||||
console.log("here");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
labels_3: {
|
||||
options = { if_return: true, conditionals: true, dead_code: true };
|
||||
input: {
|
||||
for (var i = 0; i < 5; ++i) {
|
||||
if (i < 3) continue;
|
||||
console.log(i);
|
||||
}
|
||||
};
|
||||
expect: {
|
||||
for (var i = 0; i < 5; ++i)
|
||||
i < 3 || console.log(i);
|
||||
}
|
||||
}
|
||||
|
||||
labels_4: {
|
||||
options = { if_return: true, conditionals: true, dead_code: true };
|
||||
input: {
|
||||
out: for (var i = 0; i < 5; ++i) {
|
||||
if (i < 3) continue out;
|
||||
console.log(i);
|
||||
}
|
||||
};
|
||||
expect: {
|
||||
for (var i = 0; i < 5; ++i)
|
||||
i < 3 || console.log(i);
|
||||
}
|
||||
}
|
||||
|
||||
labels_5: {
|
||||
options = { if_return: true, conditionals: true, dead_code: true };
|
||||
// should keep the break-s in the following
|
||||
input: {
|
||||
while (foo) {
|
||||
if (bar) break;
|
||||
console.log("foo");
|
||||
}
|
||||
out: while (foo) {
|
||||
if (bar) break out;
|
||||
console.log("foo");
|
||||
}
|
||||
};
|
||||
expect: {
|
||||
while (foo) {
|
||||
if (bar) break;
|
||||
console.log("foo");
|
||||
}
|
||||
out: while (foo) {
|
||||
if (bar) break out;
|
||||
console.log("foo");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
labels_6: {
|
||||
input: {
|
||||
out: break out;
|
||||
};
|
||||
expect: {}
|
||||
}
|
||||
|
||||
labels_7: {
|
||||
options = { if_return: true, conditionals: true, dead_code: true };
|
||||
input: {
|
||||
while (foo) {
|
||||
x();
|
||||
y();
|
||||
continue;
|
||||
}
|
||||
};
|
||||
expect: {
|
||||
while (foo) {
|
||||
x();
|
||||
y();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
labels_8: {
|
||||
options = { if_return: true, conditionals: true, dead_code: true };
|
||||
input: {
|
||||
while (foo) {
|
||||
x();
|
||||
y();
|
||||
break;
|
||||
}
|
||||
};
|
||||
expect: {
|
||||
while (foo) {
|
||||
x();
|
||||
y();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
labels_9: {
|
||||
options = { if_return: true, conditionals: true, dead_code: true };
|
||||
input: {
|
||||
out: while (foo) {
|
||||
x();
|
||||
y();
|
||||
continue out;
|
||||
z();
|
||||
k();
|
||||
}
|
||||
};
|
||||
expect: {
|
||||
while (foo) {
|
||||
x();
|
||||
y();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
labels_10: {
|
||||
options = { if_return: true, conditionals: true, dead_code: true };
|
||||
input: {
|
||||
out: while (foo) {
|
||||
x();
|
||||
y();
|
||||
break out;
|
||||
z();
|
||||
k();
|
||||
}
|
||||
};
|
||||
expect: {
|
||||
out: while (foo) {
|
||||
x();
|
||||
y();
|
||||
break out;
|
||||
}
|
||||
}
|
||||
}
|
||||
123
test/compress/loops.js
Normal file
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();
|
||||
}
|
||||
}
|
||||
@@ -23,3 +23,18 @@ dot_properties: {
|
||||
a["if"] = "if";
|
||||
}
|
||||
}
|
||||
|
||||
dot_properties_es5: {
|
||||
options = {
|
||||
properties: true,
|
||||
screw_ie8: true
|
||||
};
|
||||
input: {
|
||||
a["foo"] = "bar";
|
||||
a["if"] = "if";
|
||||
}
|
||||
expect: {
|
||||
a.foo = "bar";
|
||||
a.if = "if";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,3 +87,75 @@ make_sequences_4: {
|
||||
with (x = 5, obj);
|
||||
}
|
||||
}
|
||||
|
||||
lift_sequences_1: {
|
||||
options = { sequences: true };
|
||||
input: {
|
||||
foo = !(x(), y(), bar());
|
||||
}
|
||||
expect: {
|
||||
x(), y(), foo = !bar();
|
||||
}
|
||||
}
|
||||
|
||||
lift_sequences_2: {
|
||||
options = { sequences: true, evaluate: true };
|
||||
input: {
|
||||
q = 1 + (foo(), bar(), 5) + 7 * (5 / (3 - (a(), (QW=ER), c(), 2))) - (x(), y(), 5);
|
||||
}
|
||||
expect: {
|
||||
foo(), bar(), a(), QW = ER, c(), x(), y(), q = 36
|
||||
}
|
||||
}
|
||||
|
||||
lift_sequences_3: {
|
||||
options = { sequences: true, conditionals: true };
|
||||
input: {
|
||||
x = (foo(), bar(), baz()) ? 10 : 20;
|
||||
}
|
||||
expect: {
|
||||
foo(), bar(), x = baz() ? 10 : 20;
|
||||
}
|
||||
}
|
||||
|
||||
lift_sequences_4: {
|
||||
options = { side_effects: true };
|
||||
input: {
|
||||
x = (foo, bar, baz);
|
||||
}
|
||||
expect: {
|
||||
x = baz;
|
||||
}
|
||||
}
|
||||
|
||||
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;);
|
||||
}
|
||||
}
|
||||
|
||||
260
test/compress/switch.js
Normal file
260
test/compress/switch.js
Normal file
@@ -0,0 +1,260 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drop_default_1: {
|
||||
options = { dead_code: true };
|
||||
input: {
|
||||
switch (foo) {
|
||||
case 'bar': baz();
|
||||
default:
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
switch (foo) {
|
||||
case 'bar': baz();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drop_default_2: {
|
||||
options = { dead_code: true };
|
||||
input: {
|
||||
switch (foo) {
|
||||
case 'bar': baz(); break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
switch (foo) {
|
||||
case 'bar': baz();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keep_default: {
|
||||
options = { dead_code: true };
|
||||
input: {
|
||||
switch (foo) {
|
||||
case 'bar': baz();
|
||||
default:
|
||||
something();
|
||||
break;
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
switch (foo) {
|
||||
case 'bar': baz();
|
||||
default:
|
||||
something();
|
||||
}
|
||||
}
|
||||
}
|
||||
25
test/compress/typeof.js
Normal file
25
test/compress/typeof.js
Normal file
@@ -0,0 +1,25 @@
|
||||
typeof_evaluation: {
|
||||
options = {
|
||||
evaluate: true
|
||||
};
|
||||
input: {
|
||||
a = typeof 1;
|
||||
b = typeof 'test';
|
||||
c = typeof [];
|
||||
d = typeof {};
|
||||
e = typeof /./;
|
||||
f = typeof false;
|
||||
g = typeof function(){};
|
||||
h = typeof undefined;
|
||||
}
|
||||
expect: {
|
||||
a='number';
|
||||
b='string';
|
||||
c=typeof[];
|
||||
d=typeof{};
|
||||
e=typeof/./;
|
||||
f='boolean';
|
||||
g='function';
|
||||
h='undefined';
|
||||
}
|
||||
}
|
||||
@@ -73,12 +73,13 @@ function run_compress_tests() {
|
||||
var cmp = new U.Compressor(options, true);
|
||||
var expect = make_code(as_toplevel(test.expect), false);
|
||||
var input = as_toplevel(test.input);
|
||||
var input_code = make_code(test.input);
|
||||
var output = input.transform(cmp);
|
||||
output.figure_out_scope();
|
||||
output = make_code(output, false);
|
||||
if (expect != output) {
|
||||
log("!!! failed\n---INPUT---\n{input}\n---OUTPUT---\n{output}\n---EXPECTED---\n{expected}\n\n", {
|
||||
input: make_code(test.input),
|
||||
input: input_code,
|
||||
output: output,
|
||||
expected: expect
|
||||
});
|
||||
|
||||
@@ -1,27 +1,14 @@
|
||||
var save_stderr = process.stderr;
|
||||
var path = require("path");
|
||||
var fs = require("fs");
|
||||
|
||||
// discard annoying NodeJS warning ("path.existsSync is now called `fs.existsSync`.")
|
||||
var devnull = fs.createWriteStream("/dev/null");
|
||||
process.__defineGetter__("stderr", function(){
|
||||
return devnull;
|
||||
});
|
||||
|
||||
var vm = require("vm");
|
||||
var sys = require("util");
|
||||
var path = require("path");
|
||||
|
||||
var UglifyJS = vm.createContext({
|
||||
sys : sys,
|
||||
console : console,
|
||||
|
||||
MOZ_SourceMap : require("source-map")
|
||||
});
|
||||
|
||||
process.__defineGetter__("stderr", function(){
|
||||
return save_stderr;
|
||||
});
|
||||
|
||||
function load_global(file) {
|
||||
file = path.resolve(path.dirname(module.filename), file);
|
||||
try {
|
||||
@@ -65,8 +52,13 @@ for (var i in UglifyJS) {
|
||||
exports.minify = function(files, options) {
|
||||
options = UglifyJS.defaults(options, {
|
||||
outSourceMap : null,
|
||||
sourceRoot : null,
|
||||
inSourceMap : null,
|
||||
fromString : false,
|
||||
warnings : false,
|
||||
mangle : {},
|
||||
output : null,
|
||||
compress : {}
|
||||
});
|
||||
if (typeof files == "string")
|
||||
files = [ files ];
|
||||
@@ -74,40 +66,52 @@ exports.minify = function(files, options) {
|
||||
// 1. parse
|
||||
var toplevel = null;
|
||||
files.forEach(function(file){
|
||||
var code = fs.readFileSync(file, "utf8");
|
||||
var code = options.fromString
|
||||
? file
|
||||
: fs.readFileSync(file, "utf8");
|
||||
toplevel = UglifyJS.parse(code, {
|
||||
filename: file,
|
||||
filename: options.fromString ? "?" : file,
|
||||
toplevel: toplevel
|
||||
});
|
||||
});
|
||||
|
||||
// 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;
|
||||
var inMap = null;
|
||||
if (options.inSourceMap) {
|
||||
var inMap = options.inSourceMap;
|
||||
var output = {};
|
||||
if (typeof options.inSourceMap == "string") {
|
||||
inMap = fs.readFileSync(options.inSourceMap, "utf8");
|
||||
}
|
||||
if (options.outSourceMap) map = UglifyJS.SourceMap({
|
||||
file: options.outSourceMap,
|
||||
orig: inMap
|
||||
});
|
||||
var stream = UglifyJS.OutputStream({ source_map: map });
|
||||
if (options.outSourceMap) {
|
||||
output.source_map = UglifyJS.SourceMap({
|
||||
file: options.outSourceMap,
|
||||
orig: inMap,
|
||||
root: options.sourceRoot
|
||||
});
|
||||
}
|
||||
if (options.output) {
|
||||
UglifyJS.merge(output, options.output);
|
||||
}
|
||||
var stream = UglifyJS.OutputStream(output);
|
||||
toplevel.print(stream);
|
||||
return {
|
||||
code : stream + "",
|
||||
map : map + ""
|
||||
map : output.source_map + ""
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user