Compare commits

..

70 Commits

Author SHA1 Message Date
Mihai Bazon
db66eca958 v2.2.5 2013-02-14 12:51:13 +02:00
Mihai Bazon
916faf0a48 Force space after literal regexp when used in "instanceof" or "in"
Close #118
2013-02-06 11:57:59 +02:00
Mihai Bazon
f36e4e9a78 Give up evaluating (unary-prefix '-' 0)
Close #117

------

    JS, WHY YOU SUCK SO BADLY? ;-(
2013-02-06 11:51:09 +02:00
Mihai Bazon
fdf8b5eb71 Fix parens for NaN
Close #116
2013-02-06 11:38:29 +02:00
Mihai Bazon
de7ec7f1b7 Fix parens for negative numbers
Close #115
2013-02-06 11:36:04 +02:00
Mihai Bazon
3c8a0bdff4 Fix parens for AST_New
Close #114
2013-02-06 11:28:49 +02:00
Mihai Bazon
9e8ba27dcd Fix handling of constants
Close #113
2013-02-06 11:15:31 +02:00
Mihai Bazon
719a8fd102 Ugly hack to print comments before return/throw statements
Close #112
2013-02-05 19:11:39 +02:00
Mihai Bazon
3a22e917de Merge pull request #111 from mattrobenolt/safer-sourcemap
Wraps sourceMappingURL in a multiline comment. Fixes #108
2013-02-03 23:44:31 -08:00
Matt Robenolt
a9af2c9e62 Wraps sourceMappingURL in a multiline comment. Fixes #108 2013-02-03 16:01:01 -08:00
Mihai Bazon
31e99cebe7 v2.2.4 2013-02-01 13:32:15 +02:00
Mihai Bazon
a5b209470c Fix end token for Assign nodes 2013-02-01 13:32:15 +02:00
Mihai Bazon
e9a571b2a1 Merge pull request #94 from paulmillr/patch-1
Add better fromstring docs.
2013-01-31 23:50:59 -08:00
Mihai Bazon
8bf83f42ea Merge pull request #106 from gibson042/105
Fix #105: property comparison to undefined is not always safe
2013-01-24 05:51:33 -08:00
Richard Gibson
522566ea80 Fix #105: property comparison to undefined is not always safe 2013-01-23 23:52:04 -05:00
Mihai Bazon
297af47c89 Add --source-map-url option
Fix #100
Fix #47
2013-01-20 12:32:07 +02:00
Mihai Bazon
faa354f5ca [AST_Hole] the print function can be a no-op. 2013-01-17 11:36:10 +02:00
David Glasser
1529ab965a Fix output for arrays containing undefined values.
1b6bcca7 was a first attempt at this. That commit made Uglify stop replacing
holes with undefined, but instead it started replacing undefined with
holes. This is slightly problematic, because there is a difference between a
hole and an undefined value. More problematically, it changed [1,undefined] to
[1,] which generally doesn't even parse as a hole (just as a trailing comma), so
it didn't even preserve the length of the array!

Instead, parse holes as their own special AST node which prints invisibly.
2013-01-17 11:36:10 +02:00
Mihai Bazon
605f330e69 Merge pull request #98 from ForbesLindesay/patch-1
Update installation instructions
2013-01-17 01:08:59 -08:00
Mihai Bazon
f0909bdc8f Handle String() with no arguments.
Fix #91
2013-01-17 11:01:38 +02:00
Forbes Lindesay
c13e7e621d Update installation instructions re #4 2013-01-17 00:13:42 +00:00
Paul Miller
ad071f8017 Add better fromstring docs. 2013-01-13 18:45:43 +02:00
Mihai Bazon
c058d8b9cd Merge pull request #90 from jakearchibald/patch-1
Compressor options use underscores rather than hyphens
2013-01-08 14:21:25 -08:00
Jake Archibald
1d8871a092 Compressor options use underscores rather than hyphens 2013-01-08 12:33:58 -08:00
Mihai Bazon
16953c2064 v2.2.3 2013-01-04 22:50:53 +02:00
Mihai Bazon
6b14f7c224 Fix handling of labels in nested scopes 2013-01-04 14:17:33 +02:00
Mihai Bazon
130c623be7 Support output, mangle and compress options to UglifyJS.minify.
Close #57
Close #86
Close #33
2013-01-04 11:25:13 +02:00
Mihai Bazon
47c9895d59 Merge pull request #87 from BenoitZugmeyer/master
Add a --version option
2013-01-03 02:28:35 -08:00
Benoît Zugmeyer
ba403331c5 Set --version as a boolean #87 2013-01-03 11:22:37 +01:00
Benoît Zugmeyer
e82e89d1b0 --version option 2013-01-03 11:07:53 +01:00
Mihai Bazon
83a4ebfedc Implement -m sort=true
close #83
2013-01-02 12:39:00 +02:00
Mihai Bazon
9916d0e547 Accept string or number as name of an accessor.
[not sure I'm happy about this fix]

Reference mishoo/UglifyJS#478
2012-12-22 01:24:04 +02:00
Mihai Bazon
31c4a37e37 Optimize new Array(1, 2, 3) → [1, 2, 3]
Close #74
2012-12-21 21:04:35 +02:00
Mihai Bazon
08219f0cee Fix output when semicolons is off.
(need to force a semicolon for the empty body of an `if`)

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

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

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

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

function f(arg) {
        // return""+arg - no space between return and ""
        return arg.toString();
}
2012-11-11 15:53:34 +02:00
Mihai Bazon
78856a3dab declare dependency versions
close #40
2012-11-09 16:43:49 +02:00
Mihai Bazon
1e5e13ed81 AST_LabelRef no longer inherits from AST_SymbolRef 2012-11-08 15:39:14 +02:00
Mihai Bazon
64270b9778 v2.1.10 2012-11-08 12:33:27 +02:00
Mihai Bazon
e312c5c2a7 fix API breakage
close #36, #38
2012-11-08 12:31:28 +02:00
Mihai Bazon
1a5fd3e052 optimization for if/break as first statement in a loop body
for(...; x; ...) if (y) break; → for(...; x&&!y; ...);

similarly for `while` and some combinations (i.e. the `break` appears in the
`else` clause, etc.)
2012-11-08 11:43:14 +02:00
Mihai Bazon
5a7e54cf72 ignore node_modules/ 2012-11-07 15:27:12 +02:00
Mihai Bazon
39f8a62703 v2.1.9 2012-11-07 13:31:58 +02:00
Mihai Bazon
46be3f2bf1 fix another small regression
we do need parens here: `new (foo.bar().baz)`, but not here: `new foo.bar.baz`
2012-11-07 13:31:43 +02:00
Mihai Bazon
258b46f4dc v2.1.8 2012-11-07 13:03:11 +02:00
Mihai Bazon
80da21dab4 fix regression from 5346fb94 (shouldn't parenthesize i++ in x[i++]) 2012-11-07 13:02:51 +02:00
18 changed files with 931 additions and 147 deletions

1
.gitignore vendored
View File

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

View File

@@ -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,6 +49,9 @@ 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.
-p, --prefix Skip prefix for original filenames that appear in source
@@ -78,7 +88,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 +110,12 @@ map.
For example:
uglifyjs2 /home/doe/work/foo/src/js/file1.js \
/home/doe/work/foo/src/js/file2.js \
-o foo.min.js \
--source-map foo.min.js.map \
--source-map-root http://foo.com/src \
-p 5 -c -m
uglifyjs /home/doe/work/foo/src/js/file1.js \
/home/doe/work/foo/src/js/file2.js \
-o foo.min.js \
--source-map foo.min.js.map \
--source-map-root http://foo.com/src \
-p 5 -c -m
The above will compress and mangle `file1.js` and `file2.js`, will drop the
output in `foo.min.js` and the source map in `foo.min.js.map`. The source
@@ -130,7 +142,7 @@ input files from the command line.
## Mangler options
To enable the mangler you need to pass `--mangle` (`-m`). Optionally you
can pass `-m sort` (we'll possibly have other flags in the future) in order
can pass `-m sort=true` (we'll possibly have other flags in the future) in order
to assign shorter names to most frequently used variables. This saves a few
hundred bytes on jQuery before gzip, but the output is _bigger_ after gzip
(and seems to happen for other libraries I tried it on) therefore it's not
@@ -140,7 +152,7 @@ When mangling is enabled but you want to prevent certain names from being
mangled, you can declare those names with `--reserved` (`-r`) — pass a
comma-separated list of names. For example:
uglifyjs2 ... -m -r '$,require,exports'
uglifyjs ... -m -r '$,require,exports'
to prevent the `require`, `exports` and `$` names from being changed.
@@ -157,8 +169,8 @@ 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
- `dead_code` -- remove unreachable code
- `drop_debugger` -- remove `debugger;` statements
- `unsafe` -- apply "unsafe" transformations (discussion below)
- `conditionals` -- apply optimizations for `if`-s and conditional
expressions
@@ -171,11 +183,11 @@ 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
- `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
- `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
@@ -206,13 +218,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 +301,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 +323,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)
@@ -322,9 +335,10 @@ There's a single toplevel function which combines all the steps. If you
don't need additional customization, you might want to go with `minify`.
Example:
// see "fromString" below if you need to pass code instead of file name
var result = UglifyJS.minify("/path/to/file.js");
console.log(result.code); // minified output
// 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:
@@ -366,9 +380,19 @@ no sense otherwise).
Other options:
- `warnings` (default `false`) — pass `true` to display compressor warnings.
- `fromString` (default `false`) — if you pass `true` then you can pass
JavaScript source code, rather than file names.
- `mangle` — pass `false` to skip mangling names.
- `output` (default `null`) — pass an object if you wish to specify
additional [output options][codegen]. The defaults are optimized
for best compression.
- `compress` (default `{}`) — pass `false` to skip compressing entirely.
Pass an object to specify custom [compressor options][compressor].
We could add more options to `UglifyJS.minify` — if you need additional
functionality please suggest!
@@ -516,3 +540,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

View File

@@ -19,6 +19,7 @@ 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("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.")
@@ -49,6 +50,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,9 +60,11 @@ You need to pass an argument to this option to specify the name that your module
.alias("c", "compress")
.alias("d", "define")
.alias("r", "reserved")
.alias("V", "version")
.string("source-map")
.string("source-map-root")
.string("source-map-url")
.string("b")
.string("m")
.string("c")
@@ -74,6 +78,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 +87,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));
@@ -268,7 +279,7 @@ output = output.get();
if (SOURCE_MAP) {
fs.writeFileSync(ARGS.source_map, SOURCE_MAP, "utf8");
output += "\n//@ sourceMappingURL=" + ARGS.source_map;
output += "\n/*\n//@ sourceMappingURL=" + (ARGS.source_map_url || ARGS.source_map) + "\n*/";
}
if (OUTPUT_FILE) {

View File

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

View File

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

View File

@@ -59,7 +59,8 @@ function OutputStream(options) {
source_map : null,
bracketize : false,
semicolons : true,
comments : false
comments : false,
preserve_line : false
}, true);
var indentation = 0;
@@ -154,6 +155,18 @@ function OutputStream(options) {
might_need_semicolon = false;
maybe_newline();
}
if (!options.beautify && options.preserve_line && stack[stack.length - 1]) {
var target_line = stack[stack.length - 1].start.line;
while (current_line < target_line) {
OUTPUT += "\n";
current_pos++;
current_line++;
current_col = 0;
might_need_space = false;
}
}
if (might_need_space) {
var prev = last_char();
if ((is_identifier_char(prev)
@@ -327,23 +340,25 @@ function OutputStream(options) {
/* -----[ utils ]----- */
function DEFPRINT(nodetype, generator) {
nodetype.DEFMETHOD("print", function(stream){
var self = this;
stream.push_node(self);
if (self.needs_parens(stream)) {
stream.with_parens(function(){
self.add_comments(stream);
self.add_source_map(stream);
generator(self, stream);
});
} else {
nodetype.DEFMETHOD("_codegen", generator);
};
AST_Node.DEFMETHOD("print", function(stream, force_parens){
var self = this, generator = self._codegen;
stream.push_node(self);
if (force_parens || self.needs_parens(stream)) {
stream.with_parens(function(){
self.add_comments(stream);
self.add_source_map(stream);
generator(self, stream);
}
stream.pop_node();
});
};
});
} else {
self.add_comments(stream);
self.add_source_map(stream);
generator(self, stream);
}
stream.pop_node();
});
AST_Node.DEFMETHOD("print_to_string", function(options){
var s = OutputStream(options);
@@ -360,6 +375,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);
@@ -412,7 +437,7 @@ function OutputStream(options) {
PARENS(AST_Unary, function(output){
var p = output.parent();
return p instanceof AST_PropAccess;
return p instanceof AST_PropAccess && p.expression === this;
});
PARENS(AST_Seq, function(output){
@@ -454,23 +479,26 @@ function OutputStream(options) {
return true;
}
}
// for (var i = (foo in bar);;); ← perhaps useless, but valid syntax
if (this.operator == "in") {
// the “NoIn” stuff :-\
// UglifyJS 1.3.3 misses this one.
if ((p instanceof AST_For || p instanceof AST_ForIn) && p.init === this)
return true;
if (p instanceof AST_VarDef) {
var v = output.parent(1), p2 = output.parent(2);
if ((p2 instanceof AST_For || p2 instanceof AST_ForIn) && p2.init === v)
return true;
}
}
});
PARENS(AST_PropAccess, function(output){
var p = output.parent();
return p instanceof AST_New && p.expression === this;
if (p instanceof AST_New && p.expression === this) {
// i.e. new (foo.bar().baz)
//
// if there's one call into this subtree, then we need
// parens around it too, otherwise the call will be
// interpreted as passing the arguments to the upper New
// expression.
try {
this.walk(new TreeWalker(function(node){
if (node instanceof AST_Call) throw p;
}));
} catch(ex) {
if (ex !== p) throw ex;
return true;
}
}
});
PARENS(AST_Call, function(output){
@@ -481,11 +509,23 @@ function OutputStream(options) {
PARENS(AST_New, function(output){
var p = output.parent();
if (no_constructor_parens(this, output)
&& (p instanceof AST_Dot // (new Date).getTime()
&& (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;
});
function assign_and_conditional_paren_rules(output) {
var p = output.parent();
// !(a = false) → true
@@ -594,7 +634,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 {
@@ -706,7 +750,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
@@ -730,7 +774,7 @@ function OutputStream(options) {
}
else break;
}
self.body.print(output);
force_statement(self.body, output);
};
DEFPRINT(AST_If, function(self, output){
output.print("if");
@@ -838,13 +882,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);
}
});
@@ -863,7 +926,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){
@@ -891,7 +954,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(".");
}
@@ -943,8 +1006,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();
});
@@ -995,6 +1057,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");
});
@@ -1018,6 +1081,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) {

View File

@@ -881,11 +881,14 @@ function parse($TEXT, options) {
};
var function_ = function(in_statement, ctor) {
var name = is("name") ? as_symbol(in_statement
? AST_SymbolDefun
: ctor === AST_Accessor
? AST_SymbolAccessor
: 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("(");
@@ -1128,7 +1131,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));
}
@@ -1355,7 +1358,7 @@ function parse($TEXT, options) {
left : left,
operator : val,
right : maybe_assign(no_in),
end : peek()
end : prev()
});
}
croak("Invalid assignment");

View File

@@ -59,7 +59,7 @@ SymbolDef.prototype = {
unmangleable: function(options) {
return this.global
|| this.undeclared
|| (!options.eval && (this.scope.uses_eval || this.scope.uses_with));
|| (!(options && options.eval) && (this.scope.uses_eval || this.scope.uses_with));
},
mangle: function(options) {
if (!this.mangled_name && !this.unmangleable(options))
@@ -84,9 +84,12 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
if (node instanceof AST_Scope) {
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
@@ -110,9 +113,6 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
labels.del(l.name);
return true; // no descend again
}
if (node instanceof AST_SymbolDeclaration) {
node.init_scope_vars();
}
if (node instanceof AST_Symbol) {
node.scope = scope;
}
@@ -128,8 +128,6 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
// scope. Don't like this fix but seems we can't do any
// better. IE: please die. Please!
(node.scope = scope.parent_scope).def_function(node);
node.init.push(tw.parent());
}
else if (node instanceof AST_SymbolDefun) {
// Careful here, the scope where this should be defined is
@@ -138,14 +136,12 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
// instanceof AST_Scope) but we get to the symbol a bit
// later.
(node.scope = scope.parent_scope).def_function(node);
node.init.push(tw.parent());
}
else if (node instanceof AST_SymbolVar
|| node instanceof AST_SymbolConst) {
var def = scope.def_variable(node);
def.constant = node instanceof AST_SymbolConst;
def = tw.parent();
if (def.value) node.init.push(def);
def.init = tw.parent().value;
}
else if (node instanceof AST_SymbolCatch) {
// XXX: this is wrong according to ECMA-262 (12.4). the
@@ -246,10 +242,6 @@ AST_SymbolRef.DEFMETHOD("reference", function() {
this.frame = this.scope.nesting - def.scope.nesting;
});
AST_SymbolDeclaration.DEFMETHOD("init_scope_vars", function(){
this.init = [];
});
AST_Label.DEFMETHOD("init_scope_vars", function(){
this.references = [];
});
@@ -347,11 +339,16 @@ AST_Symbol.DEFMETHOD("global", function(){
return this.definition().global;
});
AST_Toplevel.DEFMETHOD("mangle_names", function(options){
options = defaults(options, {
AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){
return defaults(options, {
except : [],
eval : false,
sort : false
});
});
AST_Toplevel.DEFMETHOD("mangle_names", function(options){
options = this._default_mangler_options(options);
// We only need to mangle declaration nodes. Special logic wired
// into the code generator will display the mangled name if it's
// present (and for AST_SymbolRef-s it'll use the mangled name of
@@ -367,12 +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 p = tw.parent(), a = [];
node.variables.each(function(symbol){
if (options.except.indexOf(symbol.name) < 0) {
to_mangle.push(symbol);
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) {
@@ -387,6 +388,7 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
});
AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options){
options = this._default_mangler_options(options);
var tw = new TreeWalker(function(node){
if (node instanceof AST_Constant)
base54.consider(node.print_to_string());

View File

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

View File

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

View File

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

12
test/compress/arrays.js Normal file
View 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];
}
}

View 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 }
}

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

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

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

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

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

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

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

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

View File

@@ -56,6 +56,9 @@ exports.minify = function(files, options) {
inSourceMap : null,
fromString : false,
warnings : false,
mangle : {},
output : null,
compress : {}
});
if (typeof files == "string")
files = [ files ];
@@ -73,16 +76,20 @@ exports.minify = function(files, options) {
});
// 2. compress
toplevel.figure_out_scope();
var sq = UglifyJS.Compressor({
warnings: options.warnings,
});
toplevel = toplevel.transform(sq);
if (options.compress) {
var compress = { warnings: options.warnings };
UglifyJS.merge(compress, options.compress);
toplevel.figure_out_scope();
var sq = UglifyJS.Compressor(compress);
toplevel = toplevel.transform(sq);
}
// 3. mangle
toplevel.figure_out_scope();
toplevel.compute_char_frequency();
toplevel.mangle_names();
if (options.mangle) {
toplevel.figure_out_scope();
toplevel.compute_char_frequency();
toplevel.mangle_names(options.mangle);
}
// 4. output
var map = null;
@@ -95,7 +102,11 @@ exports.minify = function(files, options) {
orig: inMap,
root: options.sourceRoot
});
var stream = UglifyJS.OutputStream({ source_map: map });
var output = { source_map: map };
if (options.output) {
UglifyJS.merge(output, options.output);
}
var stream = UglifyJS.OutputStream(output);
toplevel.print(stream);
return {
code : stream + "",