Compare commits

...

76 Commits

Author SHA1 Message Date
Alex Lam S.L
4231f7323e v3.0.16 2017-06-14 16:45:09 +08:00
kzc
da2de350c3 add comment about quote_style and gzip (#2092) 2017-06-14 12:23:03 +08:00
Alex Lam S.L
41beae4dd7 cache web assets between CI runs (#2089)
- skip `test/jetstream.js` for `node@0.12`
2017-06-14 11:53:10 +08:00
Ziad El Khoury Hanna
82db9188ac fix CLI parsing of --source-map content (#2088)
fixes #2082
2017-06-13 16:30:46 +08:00
Alex Lam S.L
3dc9e140e4 add Node.js 8 to Travis CI (#2086)
- explicitly terminate `test/jetstream.js` upon completion
- log verbose output from `test/benchmark.js` & `test/jetstream.js`
- remove obsolete workaround for Travis CI
2017-06-13 06:21:16 +08:00
Alex Lam S.L
fed0096556 allow expect_stdout to specify Error (#2087) 2017-06-13 04:57:26 +08:00
Alex Lam S.L
2bdc8802dd fix variable accounting in inline (#2085)
fixes #2084
2017-06-13 01:40:14 +08:00
Alex Lam S.L
5ef7cb372a suppress false positives for-in loops (#2080)
fixes #2079
2017-06-10 13:55:17 +08:00
Alex Lam S.L
4ad7b1dae4 fix portability of sandbox.run_code() on Node.js 0.1x (#2078) 2017-06-10 01:08:58 +08:00
Alex Lam S.L
9186859cb7 fix non-string parameters (#2076)
`Stream.write()` is not as versatile as `console.log()`
2017-06-10 00:11:40 +08:00
Alex Lam S.L
47c0713747 report test/ufuzz.js failures in process.stderr (#2074) 2017-06-09 15:56:28 +08:00
Alex Lam S.L
293c566d6c marshal mangle[.properties].reserved from non-Array values (#2072) 2017-06-09 04:29:12 +08:00
Alex Lam S.L
9c306406f1 fix iteration over object with inherited properties (#2068)
fixes #2055
2017-06-08 03:27:03 +08:00
Alex Lam S.L
9db0695b10 fix cascade on multi-branch evaluations (#2067)
Partially reverts #2059 as this has better coverage and performance.

fixes #2062
2017-06-07 19:52:01 +08:00
Alex Lam S.L
f2af093402 fix CLI output corruption (#2061)
Using `console.error()` & `console.log()` result in inconsistent formatting across Node.js versions.

Avoid this issue by directly writing to `process.stderr` & `process.stdout` instead.

Miscellaneous
- prettify invalid option listing
2017-06-07 04:25:32 +08:00
Alex Lam S.L
b9ad53d1ab fix inline handling of AST_Call.args (#2059) 2017-06-06 22:55:25 +08:00
Alex Lam S.L
b0eab71470 implement test/jetstream.js --debug (#2058) 2017-06-06 19:28:12 +08:00
Alex Lam S.L
3493a182b2 implement function inlining (#2053)
- empty body
- single `AST_Return`
- single `AST_SimpleStatement`
- avoid `/*#__PURE__*/`

Miscellaneous
- enhance single-use function substitution

fixes #281
2017-06-06 05:49:53 +08:00
Alex Lam S.L
27c5284d3d workaround webkit parsing error (#2056)
apply `webkit` to jetstream tests
2017-06-06 04:06:42 +08:00
Alex Lam S.L
540220b91b fix AST_Function scope invariance (#2052)
improve function name hack in `run_code()`
2017-06-04 19:27:43 +08:00
Alex Lam S.L
84634da4b5 add tests for AST_SymbolAccessor (#2049) 2017-06-03 16:08:10 +08:00
Alex Lam S.L
1743621889 clean up lib/parse.js (#2047)
- remove unused definitions
- replace `array_to_hash()`
2017-06-03 14:00:59 +08:00
kzc
f330ab743a better document behavior of unsafe_Func (#2043) 2017-06-02 12:07:17 +08:00
Alex Lam S.L
4377e932ca v3.0.15 2017-06-01 18:12:38 +08:00
Alex Lam S.L
bac14ba881 fix non-identifier getter/setter name (#2041)
fixes #2040
2017-06-01 18:11:16 +08:00
Alex Lam S.L
ec095ed647 whitelist unsafe evaluate candidates (#2039)
- all arguments may accept constant values
- return constant value
- free of side effects
- available & identical across locales and runtime environments
2017-06-01 04:33:05 +08:00
Alex Lam S.L
17e73121fa enhance unsafe evaluate (#2037) 2017-06-01 00:56:28 +08:00
kzc
f71e8fd948 reformat mangle options section of README (#2036) 2017-05-31 21:52:43 +08:00
Alex Lam S.L
3e62faa64f v3.0.14 2017-05-31 11:34:51 +08:00
Alex Lam S.L
e9645e017f introduce unsafe_Func (#2033)
Separate flag for #203 functionality.
2017-05-31 03:38:00 +08:00
Alex Lam S.L
55b5f2a8aa widen CLI parse error code fragment displayed (#2032)
fixes #2030
2017-05-31 01:56:52 +08:00
Alex Lam S.L
4e0a22e5c8 v3.0.13 2017-05-29 10:52:13 +08:00
Alex Lam S.L
1aa38051fb better fix for #512 & #2010 (#2019)
- remove duplicated functionalities
- fix similar issue with `else`
2017-05-29 10:51:41 +08:00
Alex Lam S.L
e62b879b48 display default values in --help options (#2018) 2017-05-28 22:57:20 +08:00
Alex Lam S.L
c6c9f4f5a8 implement --help options (#2017) 2017-05-28 18:21:44 +08:00
Alex Lam S.L
fec14379f6 improve CLI usability (#2016)
Report supported options upon invalid option syntax.

fixes #1883
2017-05-28 04:09:40 +08:00
Alex Lam S.L
79131cd647 extend node_version range on applicable tests (#2015) 2017-05-27 22:18:28 +08:00
Alex Lam S.L
c3f14a1481 v3.0.12 2017-05-27 18:08:09 +08:00
Alex Lam S.L
7b13159cda fix hoist_funs on block-scoped function under "use strict" (#2013)
Technically not part of ES5, but commonly used code exists in the wild.
2017-05-27 17:44:59 +08:00
Alex Lam S.L
95094b9c22 fix if_return on AST_Defun (#2010)
Previous fiix for #1052 perturbs declaration order of functions which leads to incorrect behaviour under "use strict".
2017-05-27 13:41:49 +08:00
kzc
1ff8e9dd38 clarify what --mangle-props does (#2012) 2017-05-27 13:17:30 +08:00
kzc
78309a293d better document mangle properties options (#2009) 2017-05-27 02:28:43 +08:00
kzc
695e182d59 fix and expand --mangle-props documentation (#2008)
fixes #2007
2017-05-27 01:25:51 +08:00
Alex Lam S.L
dc33facfcb fix dead_code on block-scoped function under "use strict" (#2006)
Technically not part of ES5, but commonly used code exists in the wild.
2017-05-26 16:08:51 +08:00
Alex Lam S.L
c70fb60384 clean up lib/scope.js (#2003)
fixes #2004
2017-05-26 03:58:35 +08:00
Alex Lam S.L
793d61499b report timing breakdown (#2000)
fix corner cases with `sourceMap`

fixes #1998
2017-05-25 07:15:55 +08:00
Alex Lam S.L
a277fe168d ensure new line after describe_ast() (#1999) 2017-05-25 02:32:36 +08:00
Alex Lam S.L
7d3b941e6e reinstate describe_ast() on CLI (#1996)
fixes #1995
2017-05-24 02:30:09 +08:00
Alex Lam S.L
e95052a423 v3.0.11 2017-05-23 22:26:59 +08:00
Alex Lam S.L
e667f0acb8 fix source map offset (#1993)
Account for whitespace insertions.

fixes #505
fixes #890
2017-05-23 20:25:48 +08:00
kzc
69ac794bc8 add another minify() options example (#1988) 2017-05-22 12:19:07 +08:00
Alex Lam S.L
efdb65913b improve usability of global_defs in minify() (#1987)
Use `@key` to `parse()` string value as `AST_Node`.

fixes #1986
2017-05-22 01:38:43 +08:00
kzc
a1dedeb3ce more refinement of minify() documentation (#1983) 2017-05-21 04:55:03 +08:00
Alex Lam S.L
d3c4a8e9e7 v3.0.10 2017-05-21 01:30:17 +08:00
kzc
7e164aba8f add "es5" to package.json keywords (#1980) 2017-05-20 22:09:50 +08:00
kzc
22aedef849 document minify() option toplevel (#1979) 2017-05-20 22:09:21 +08:00
Alex Lam S.L
58fae7dc07 enhance if_return to handle return void... (#1977)
fixes #512
2017-05-20 15:58:46 +08:00
kzc
5bf8d7e949 document 3.x minify() does not throw errors (#1975) 2017-05-20 10:49:35 +08:00
kzc
1df9d06f4a document minify warnings and add an error example (#1973) 2017-05-19 17:20:21 +08:00
Alex Lam S.L
3408fc9d32 v3.0.9 2017-05-19 09:35:26 +08:00
Alex Lam S.L
eae26756f1 introduce unsafe_regexp (#1970)
fixes #1964
2017-05-19 09:06:29 +08:00
Alex Lam S.L
43add9416b v3.0.8 2017-05-18 14:49:40 +08:00
Alex Lam S.L
efcf167e5e make expect_stdout node version specific (#1963)
... via semver string on `node_version` label.
2017-05-18 11:28:35 +08:00
Kara
6ed90913ca fix docs for side_effects flag to reflect current behavior (#1966) 2017-05-18 10:51:49 +08:00
Alex Lam S.L
569c21e952 improve RegExp handling (#1959)
- remove `options.output.unescape_regexps`
- preserve original pattern whenever possible

fixes #54
fixes #1929
2017-05-17 20:10:50 +08:00
Alex Lam S.L
87c3a2c0ce remove space_colon (#1960)
Always emit space after colon when `options.output.beautify` is enabled.
2017-05-17 14:07:34 +08:00
Rob Garrison
baef8bf050 update output options in readme (#1958) 2017-05-17 11:54:46 +08:00
alexlamsl
0813c5316f remove Travis CI badge 2017-05-17 10:32:59 +08:00
Alex Lam S.L
c88139492d v3.0.7 2017-05-16 19:59:40 +08:00
Alex Lam S.L
cb45886512 export TreeTransformer (#1950)
- link to existing documentation on `TreeWalker` & `TreeTransformer`
- fix Travis build failures

fixes #1949
2017-05-16 19:59:05 +08:00
Alex Lam S.L
050474ab44 v3.0.6 2017-05-16 06:38:58 +08:00
Alex Lam S.L
f6c805ae1d print package name alongside version in CLI (#1946)
fixes #1945
2017-05-16 06:34:32 +08:00
Alex Lam S.L
9464d3c20f fix parsing of property access after new line (#1944)
Account for comments when detecting property access in `tokenizer`.

fixes #1943
2017-05-16 05:40:49 +08:00
alexlamsl
f18abd1b9c minor fixes to README.md 2017-05-16 01:33:01 +08:00
kzc
3be06ad085 reorg README for 3.x (#1942) 2017-05-16 01:12:00 +08:00
Alex Lam S.L
265008c948 improve keyword-related parser errors (#1941)
fixes #1937
2017-05-15 23:02:55 +08:00
51 changed files with 3005 additions and 1010 deletions

View File

@@ -1,12 +1,14 @@
language: node_js language: node_js
before_install: "npm install -g npm"
node_js: node_js:
- "0.10" - "0.10"
- "0.12" - "0.12"
- "4" - "4"
- "6" - "6"
- "8"
env: env:
- UGLIFYJS_TEST_ALL=1 - UGLIFYJS_TEST_ALL=1
matrix: matrix:
fast_finish: true fast_finish: true
sudo: false sudo: false
cache:
directories: tmp

771
README.md
View File

@@ -1,11 +1,10 @@
UglifyJS 3 UglifyJS 3
========== ==========
[![Build Status](https://travis-ci.org/mishoo/UglifyJS2.svg)](https://travis-ci.org/mishoo/UglifyJS2)
UglifyJS is a JavaScript parser, minifier, compressor and beautifier toolkit. UglifyJS is a JavaScript parser, minifier, compressor and beautifier toolkit.
#### Note: #### Note:
- **`uglify-js@3.x` has a simplified API and CLI that is not backwards compatible with [`uglify-js@2.x`](https://github.com/mishoo/UglifyJS2/tree/v2.x)**. - **`uglify-js@3` has a simplified [API](#api-reference) and [CLI](#command-line-usage) that is not backwards compatible with [`uglify-js@2`](https://github.com/mishoo/UglifyJS2/tree/v2.x)**.
- **Documentation for UglifyJS `2.x` releases can be found [here](https://github.com/mishoo/UglifyJS2/tree/v2.x)**. - **Documentation for UglifyJS `2.x` releases can be found [here](https://github.com/mishoo/UglifyJS2/tree/v2.x)**.
- `uglify-js` only supports ECMAScript 5 (ES5). - `uglify-js` only supports ECMAScript 5 (ES5).
- Those wishing to minify - Those wishing to minify
@@ -25,8 +24,7 @@ From NPM for programmatic use:
npm install uglify-js npm install uglify-js
Usage # Command line usage
-----
uglifyjs [input files] [options] uglifyjs [input files] [options]
@@ -43,10 +41,11 @@ a double dash to prevent input files being used as option arguments:
uglifyjs --compress --mangle -- input.js uglifyjs --compress --mangle -- input.js
The available options are: ### Command line options
``` ```
-h, --help Print usage information. -h, --help Print usage information.
`--help options` for details on available options.
-V, --version Print version number. -V, --version Print version number.
-p, --parse <options> Specify parser options: -p, --parse <options> Specify parser options:
`acorn` Use Acorn for parsing. `acorn` Use Acorn for parsing.
@@ -128,8 +127,8 @@ The available options are:
the source map. the source map.
`url` If specified, path to the source map to append in `url` If specified, path to the source map to append in
`//# sourceMappingURL`. `//# sourceMappingURL`.
--stats Display operations run time on STDERR. --timings Display operations run time on STDERR.
--toplevel Compress and/or mangle variables in toplevel scope. --toplevel Compress and/or mangle variables in top level scope.
--verbose Print diagnostic messages. --verbose Print diagnostic messages.
--warn Print warning messages. --warn Print warning messages.
--wrap <name> Embed everything in a big function, making the --wrap <name> Embed everything in a big function, making the
@@ -142,7 +141,7 @@ The available options are:
Specify `--output` (`-o`) to declare the output file. Otherwise the output Specify `--output` (`-o`) to declare the output file. Otherwise the output
goes to STDOUT. goes to STDOUT.
## Source map options ## CLI source map options
UglifyJS can generate a source map file, which is highly useful for UglifyJS can generate a source map file, which is highly useful for
debugging your compressed JavaScript. To get a source map, pass debugging your compressed JavaScript. To get a source map, pass
@@ -186,12 +185,25 @@ To use this feature pass `--source-map content="/path/to/input/source.map"`
or `--source-map content=inline` if the source map is included inline with or `--source-map content=inline` if the source map is included inline with
the sources. the sources.
## Mangler options ## CLI compress options
You need to pass `--compress` (`-c`) to enable the compressor. Optionally
you can pass a comma-separated list of [compress options](#compress-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`).
Example:
uglifyjs file.js -c toplevel,sequences=false
## CLI mangle options
To enable the mangler you need to pass `--mangle` (`-m`). The following To enable the mangler you need to pass `--mangle` (`-m`). The following
(comma-separated) options are supported: (comma-separated) options are supported:
- `toplevel` — mangle names declared in the toplevel scope (disabled by - `toplevel` — mangle names declared in the top level scope (disabled by
default). default).
- `eval` — mangle names visible in scopes where `eval` or `with` are used - `eval` — mangle names visible in scopes where `eval` or `with` are used
@@ -205,26 +217,56 @@ comma-separated list of names. For example:
to prevent the `require`, `exports` and `$` names from being changed. to prevent the `require`, `exports` and `$` names from being changed.
### Mangling property names (`--mangle-props`) ### CLI mangling property names (`--mangle-props`)
**Note:** this will probably break your code. Mangling property names is a **Note:** THIS WILL PROBABLY BREAK YOUR CODE. Mangling property names
separate step, different from variable name mangling. Pass is a separate step, different from variable name mangling. Pass
`--mangle-props`. It will mangle all properties that are seen in some `--mangle-props` to enable it. It will mangle all properties in the
object literal, or that are assigned to. For example: input code with the exception of built in DOM properties and properties
in core javascript classes. For example:
```js ```javascript
// example.js
var x = { var x = {
foo: 1 baz_: 0,
foo_: 1,
calc: function() {
return this.foo_ + this.baz_;
}
}; };
x.bar_ = 2;
x.bar = 2; x["baz_"] = 3;
x["baz"] = 3; console.log(x.calc());
x[condition ? "moo" : "boo"] = 4; ```
console.log(x.something()); Mangle all properties (except for javascript `builtins`):
```bash
$ uglifyjs example.js -c -m --mangle-props
```
```javascript
var x={o:0,_:1,l:function(){return this._+this.o}};x.t=2,x.o=3,console.log(x.l());
```
Mangle all properties except for `reserved` properties:
```bash
$ uglifyjs example.js -c -m --mangle-props reserved=[foo_,bar_]
```
```javascript
var x={o:0,foo_:1,_:function(){return this.foo_+this.o}};x.bar_=2,x.o=3,console.log(x._());
```
Mangle all properties matching a `regex`:
```bash
$ uglifyjs example.js -c -m --mangle-props regex=/_$/
```
```javascript
var x={o:0,_:1,calc:function(){return this._+this.o}};x.l=2,x.o=3,console.log(x.calc());
``` ```
In the above code, `foo`, `bar`, `baz`, `moo` and `boo` will be replaced Combining mangle properties options:
with single characters, while `something()` will be left as is. ```bash
$ uglifyjs example.js -c -m --mangle-props regex=/_$/,reserved=[bar_]
```
```javascript
var x={o:0,_:1,calc:function(){return this._+this.o}};x.bar_=2,x.o=3,console.log(x.calc());
```
In order for this to be of any use, we avoid mangling standard JS names by In order for this to be of any use, we avoid mangling standard JS names by
default (`--mangle-props builtins` to override). default (`--mangle-props builtins` to override).
@@ -233,7 +275,7 @@ A default exclusion file is provided in `tools/domprops.json` which should
cover most standard JS and DOM properties defined in various browsers. Pass cover most standard JS and DOM properties defined in various browsers. Pass
`--mangle-props domprops` to disable this feature. `--mangle-props domprops` to disable this feature.
You can also use a regular expression to define which property names should be A regular expression can be used to define which property names should be
mangled. For example, `--mangle-props regex=/^_/` will only mangle property mangled. For example, `--mangle-props regex=/^_/` will only mangle property
names that start with an underscore. names that start with an underscore.
@@ -243,10 +285,10 @@ mangled to the same name in all of them. For this, pass `--name-cache filename.
and UglifyJS will maintain these mappings in a file which can then be reused. and UglifyJS will maintain these mappings in a file which can then be reused.
It should be initially empty. Example: It should be initially empty. Example:
``` ```bash
rm -f /tmp/cache.json # start fresh $ rm -f /tmp/cache.json # start fresh
uglifyjs file1.js file2.js --mangle-props --name-cache /tmp/cache.json -o part1.js $ uglifyjs file1.js file2.js --mangle-props --name-cache /tmp/cache.json -o part1.js
uglifyjs file3.js file4.js --mangle-props --name-cache /tmp/cache.json -o part2.js $ uglifyjs file3.js file4.js --mangle-props --name-cache /tmp/cache.json -o part2.js
``` ```
Now, `part1.js` and `part2.js` will be consistent with each other in terms Now, `part1.js` and `part2.js` will be consistent with each other in terms
@@ -255,18 +297,29 @@ of mangled property names.
Using the name cache is not necessary if you compress all your files in a Using the name cache is not necessary if you compress all your files in a
single call to UglifyJS. single call to UglifyJS.
#### Mangling unquoted names (`--mangle-props keep_quoted`) ### Mangling unquoted names (`--mangle-props keep_quoted`)
Using quoted property name (`o["foo"]`) reserves the property name (`foo`) Using quoted property name (`o["foo"]`) reserves the property name (`foo`)
so that it is not mangled throughout the entire script even when used in an so that it is not mangled throughout the entire script even when used in an
unquoted style (`o.foo`). Example: unquoted style (`o.foo`). Example:
```javascript
// stuff.js
var o = {
"foo": 1,
bar: 3
};
o.foo += o.bar;
console.log(o.foo);
``` ```
$ echo 'var o={"foo":1, bar:3}; o.foo += o.bar; console.log(o.foo);' | uglifyjs --mangle-props keep_quoted -mc ```bash
var o={foo:1,a:3};o.foo+=o.a,console.log(o.foo); $ uglifyjs stuff.js --mangle-props keep_quoted -c -m
```
```javascript
var o={foo:1,o:3};o.foo+=o.o,console.log(o.foo);
``` ```
#### Debugging property name mangling ### Debugging property name mangling
You can also pass `--mangle-props debug` in order to mangle property names You can also pass `--mangle-props debug` in order to mangle property names
without completely obscuring them. For example the property `o.foo` without completely obscuring them. For example the property `o.foo`
@@ -274,6 +327,13 @@ would mangle to `o._$foo$_` with this option. This allows property mangling
of a large codebase while still being able to debug the code and identify of a large codebase while still being able to debug the code and identify
where mangling is breaking things. where mangling is breaking things.
```bash
$ uglifyjs stuff.js --mangle-props debug -c -m
```
```javascript
var o={_$foo$_:1,_$bar$_:3};o._$foo$_+=o._$bar$_,console.log(o._$foo$_);
```
You can also pass a custom suffix using `--mangle-props debug=XYZ`. This would then You can also pass a custom suffix using `--mangle-props debug=XYZ`. This would then
mangle `o.foo` to `o._$foo$XYZ_`. You can change this each time you compile a mangle `o.foo` to `o._$foo$XYZ_`. You can change this each time you compile a
script to identify how a property got mangled. One technique is to pass a script to identify how a property got mangled. One technique is to pass a
@@ -281,12 +341,211 @@ random number on every compile to simulate mangling changing with different
inputs (e.g. as you update the input script with new properties), and to help inputs (e.g. as you update the input script with new properties), and to help
identify mistakes like writing mangled keys to storage. identify mistakes like writing mangled keys to storage.
## Compressor options
You need to pass `--compress` (`-c`) to enable the compressor. Optionally # API Reference
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 Assuming installation via NPM, you can load UglifyJS in your application
to set `true`; it's effectively a shortcut for `foo=true`). like this:
```javascript
var UglifyJS = require("uglify-js");
```
There is a single high level function, **`minify(code, options)`**,
which will perform all minification [phases](#minify-options) in a configurable
manner. By default `minify()` will enable the options [`compress`](#compress-options)
and [`mangle`](#mangle-options). Example:
```javascript
var code = "function add(first, second) { return first + second; }";
var result = UglifyJS.minify(code);
console.log(result.error); // runtime error, or `undefined` if no error
console.log(result.code); // minified output: function add(n,d){return n+d}
```
You can `minify` more than one JavaScript file at a time by using an object
for the first argument where the keys are file names and the values are source
code:
```javascript
var code = {
"file1.js": "function add(first, second) { return first + second; }",
"file2.js": "console.log(add(1 + 2, 3 + 4));"
};
var result = UglifyJS.minify(code);
console.log(result.code);
// function add(d,n){return d+n}console.log(add(3,7));
```
The `toplevel` option:
```javascript
var code = {
"file1.js": "function add(first, second) { return first + second; }",
"file2.js": "console.log(add(1 + 2, 3 + 4));"
};
var options = { toplevel: true };
var result = UglifyJS.minify(code, options);
console.log(result.code);
// console.log(function(n,o){return n+o}(3,7));
```
An example of a combination of `minify()` options:
```javascript
var code = {
"file1.js": "function add(first, second) { return first + second; }",
"file2.js": "console.log(add(1 + 2, 3 + 4));"
};
var options = {
toplevel: true,
compress: {
global_defs: {
"@console.log": "alert"
},
passes: 2
},
output: {
beautify: false,
preamble: "/* uglified */"
}
};
var result = UglifyJS.minify(code, options);
console.log(result.code);
// /* uglified */
// alert(10);"
```
To produce warnings:
```javascript
var code = "function f(){ var u; return 2 + 3; }";
var options = { warnings: true };
var result = UglifyJS.minify(code, options);
console.log(result.error); // runtime error, `undefined` in this case
console.log(result.warnings); // [ 'Dropping unused variable u [0:1,18]' ]
console.log(result.code); // function f(){return 5}
```
An error example:
```javascript
var result = UglifyJS.minify({"foo.js" : "if (0) else console.log(1);"});
console.log(JSON.stringify(result.error));
// {"message":"Unexpected token: keyword (else)","filename":"foo.js","line":1,"col":7,"pos":7}
```
Note: unlike `uglify-js@2.x`, the `3.x` API does not throw errors. To
achieve a similar effect one could do the following:
```javascript
var result = UglifyJS.minify(code, options);
if (result.error) throw result.error;
```
## Minify options
- `warnings` (default `false`) — pass `true` to return compressor warnings
in `result.warnings`. Use the value `"verbose"` for more detailed warnings.
- `parse` (default `{}`) — pass an object if you wish to specify some
additional [parse options](#parse-options).
- `compress` (default `{}`) — pass `false` to skip compressing entirely.
Pass an object to specify custom [compress options](#compress-options).
- `mangle` (default `true`) — pass `false` to skip mangling names, or pass
an object to specify [mangle options](#mangle-options) (see below).
- `mangle.properties` (default `false`) — a subcategory of the mangle option.
Pass an object to specify custom [mangle property options](#mangle-properties-options).
- `output` (default `null`) — pass an object if you wish to specify
additional [output options](#output-options). The defaults are optimized
for best compression.
- `sourceMap` (default `false`) - pass an object if you wish to specify
[source map options](#source-map-options).
- `toplevel` (default `false`) - set to `true` if you wish to enable top level
variable and function name mangling and to drop unused variables and functions.
- `ie8` (default `false`) - set to `true` to support IE8.
## Minify options structure
```javascript
{
warnings: false,
parse: {
// parse options
},
compress: {
// compress options
},
mangle: {
// mangle options
properties: {
// mangle property options
}
},
output: {
// output options
},
sourceMap: {
// source map options
},
toplevel: false,
ie8: false,
}
```
### Source map options
To generate a source map:
```javascript
var result = UglifyJS.minify({"file1.js": "var a = function() {};"}, {
sourceMap: {
filename: "out.js",
url: "out.js.map"
}
});
console.log(result.code); // minified output
console.log(result.map); // source map
```
Note that the source map is not saved in a file, it's just returned in
`result.map`. The value passed for `sourceMap.url` is only used to set
`//# sourceMappingURL=out.js.map` in `result.code`. The value of
`filename` is only used to set `file` attribute (see [the spec][sm-spec])
in source map file.
You can set option `sourceMap.url` to be `"inline"` and source map will
be appended to code.
You can also specify sourceRoot property to be included in source map:
```javascript
var result = UglifyJS.minify({"file1.js": "var a = function() {};"}, {
sourceMap: {
root: "http://example.com/src",
url: "out.js.map"
}
});
```
If you're compressing compiled JavaScript and have a source map for it, you
can use `sourceMap.content`:
```javascript
var result = UglifyJS.minify({"compiled.js": "compiled code"}, {
sourceMap: {
content: "content from compiled.js.map",
url: "minified.js.map"
}
});
// same as before, it returns `code` and `map`
```
If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.url`.
## Parse options
- `bare_returns` (default `false`) -- support top level `return` statements
- `html5_comments` (default `true`)
- `shebang` (default `true`) -- support `#!command` as the first line
## Compress options
- `sequences` (default: true) -- join consecutive simple statements using the - `sequences` (default: true) -- join consecutive simple statements using the
comma operator. May be set to a positive integer to specify the maximum number comma operator. May be set to a positive integer to specify the maximum number
@@ -313,12 +572,18 @@ to set `true`; it's effectively a shortcut for `foo=true`).
comparison are switching. Compression only works if both `comparisons` and comparison are switching. Compression only works if both `comparisons` and
`unsafe_comps` are both set to true. `unsafe_comps` are both set to true.
- `unsafe_Func` (default: false) -- compress and mangle `Function(args, code)`
when both `args` and `code` are string literals.
- `unsafe_math` (default: false) -- optimize numerical expressions like - `unsafe_math` (default: false) -- optimize numerical expressions like
`2 * x * 3` into `6 * x`, which may give imprecise floating point results. `2 * x * 3` into `6 * x`, which may give imprecise floating point results.
- `unsafe_proto` (default: false) -- optimize expressions like - `unsafe_proto` (default: false) -- optimize expressions like
`Array.prototype.slice.call(a)` into `[].slice.call(a)` `Array.prototype.slice.call(a)` into `[].slice.call(a)`
- `unsafe_regexp` (default: false) -- enable substitutions of variables with
`RegExp` values the same way as if they are constants.
- `conditionals` -- apply optimizations for `if`-s and conditional - `conditionals` -- apply optimizations for `if`-s and conditional
expressions expressions
@@ -338,7 +603,7 @@ to set `true`; it's effectively a shortcut for `foo=true`).
assignments do not count as references unless set to `"keep_assign"`) assignments do not count as references unless set to `"keep_assign"`)
- `toplevel` -- drop unreferenced functions (`"funcs"`) and/or variables (`"vars"`) - `toplevel` -- drop unreferenced functions (`"funcs"`) and/or variables (`"vars"`)
in the toplevel scope (`false` by default, `true` to drop both unreferenced in the top level scope (`false` by default, `true` to drop both unreferenced
functions and variables) functions and variables)
- `top_retain` -- prevent specific toplevel functions and variables from `unused` - `top_retain` -- prevent specific toplevel functions and variables from `unused`
@@ -351,13 +616,15 @@ to set `true`; it's effectively a shortcut for `foo=true`).
- `if_return` -- optimizations for if/return and if/continue - `if_return` -- optimizations for if/return and if/continue
- `inline` -- embed simple functions
- `join_vars` -- join consecutive `var` statements - `join_vars` -- join consecutive `var` statements
- `cascade` -- small optimization for sequences, transform `x, x` into `x` - `cascade` -- small optimization for sequences, transform `x, x` into `x`
and `x = something(), x` into `x = something()` and `x = something(), x` into `x = something()`
- `collapse_vars` -- Collapse single-use `var` and `const` definitions - `collapse_vars` -- Collapse single-use non-constant variables - side
when possible. effects permitting.
- `reduce_vars` -- Improve optimization on variables assigned with and - `reduce_vars` -- Improve optimization on variables assigned with and
used as constant values. used as constant values.
@@ -409,13 +676,145 @@ to set `true`; it's effectively a shortcut for `foo=true`).
- `keep_infinity` -- default `false`. Pass `true` to prevent `Infinity` from - `keep_infinity` -- default `false`. Pass `true` to prevent `Infinity` from
being compressed into `1/0`, which may cause performance issues on Chrome. being compressed into `1/0`, which may cause performance issues on Chrome.
- `side_effects` -- default `false`. Pass `true` to potentially drop functions - `side_effects` -- default `true`. Pass `false` to disable potentially dropping
marked as "pure". A function call is marked as "pure" if a comment annotation functions marked as "pure". A function call is marked as "pure" if a comment
`/*@__PURE__*/` or `/*#__PURE__*/` immediately precedes the call. For example: annotation `/*@__PURE__*/` or `/*#__PURE__*/` immediately precedes the call. For
`/*@__PURE__*/foo()`; example: `/*@__PURE__*/foo();`
## Mangle options
### The `unsafe` option - `reserved` (default `[]`). Pass an array of identifiers that should be
excluded from mangling. Example: `["foo", "bar"]`.
- `toplevel` (default `false`). Pass `true` to mangle names declared in the
top level scope.
- `keep_fnames` (default `false`). Pass `true` to not mangle function names.
Useful for code relying on `Function.prototype.name`. See also: the `keep_fnames`
[compress option](#compress-options).
- `eval` (default `false`). Pass `true` to mangle names visible in scopes
where `eval` or `with` are used.
Examples:
```javascript
// test.js
var globalVar;
function funcName(firstLongName, anotherLongName) {
var myVariable = firstLongName + anotherLongName;
}
```
```javascript
var code = fs.readFileSync("test.js", "utf8");
UglifyJS.minify(code).code;
// 'function funcName(a,n){}var globalVar;'
UglifyJS.minify(code, { mangle: { reserved: ['firstLongName'] } }).code;
// 'function funcName(firstLongName,a){}var globalVar;'
UglifyJS.minify(code, { mangle: { toplevel: true } }).code;
// 'function n(n,a){}var a;'
```
### Mangle properties options
- `reserved` (default: `[]`) -- Do not mangle property names listed in the
`reserved` array.
- `regex` (default: `null`) -— Pass a RegExp literal to only mangle property
names matching the regular expression.
- `keep_quoted` (default: `false`) -— Only mangle unquoted property names.
- `debug` (default: `false`) -— Mangle names with the original name still present.
Pass an empty string `""` to enable, or a non-empty string to set the debug suffix.
- `builtins` (default: `false`) -- Use `true` to allow the mangling of builtin
DOM properties. Not recommended to override this setting.
## Output options
The code generator tries to output shortest code possible by default. In
case you want beautified output, pass `--beautify` (`-b`). Optionally you
can pass additional arguments that control the code output:
- `ascii_only` (default `false`) -- escape Unicode characters in strings and
regexps (affects directives with non-ascii characters becoming invalid)
- `beautify` (default `true`) -- whether to actually beautify the output.
Passing `-b` will set this to true, but you might need to pass `-b` even
when you want to generate minified code, in order to specify additional
arguments, so you can use `-b beautify=false` to override it.
- `bracketize` (default `false`) -- always insert brackets in `if`, `for`,
`do`, `while` or `with` statements, even if their body is a single
statement.
- `comments` (default `false`) -- pass `true` or `"all"` to preserve all
comments, `"some"` to preserve some comments, a regular expression string
(e.g. `/^!/`) or a function.
- `indent_level` (default 4)
- `indent_start` (default 0) -- prefix all lines by that many spaces
- `inline_script` (default `false`) -- escape the slash in occurrences of
`</script` in strings
- `keep_quoted_props` (default `false`) -- when turned on, prevents stripping
quotes from property names in object literals.
- `max_line_len` (default `false`) -- maximum line length (for uglified code)
- `preamble` (default `null`) -- when passed it must be a string and
it will be prepended to the output literally. The source map will
adjust for this text. Can be used to insert a comment containing
licensing information, for example.
- `preserve_line` (default `false`) -- pass `true` to preserve lines, but it
only works if `beautify` is set to `false`.
- `quote_keys` (default `false`) -- pass `true` to quote all keys in literal
objects
- `quote_style` (default `0`) -- preferred quote style for strings (affects
quoted property names and directives as well):
- `0` -- prefers double quotes, switches to single quotes when there are
more double quotes in the string itself. `0` is best for gzip size.
- `1` -- always use single quotes
- `2` -- always use double quotes
- `3` -- always use the original quotes
- `semicolons` (default `true`) -- separate statements with semicolons. If
you pass `false` then whenever possible we will use a newline instead of a
semicolon, leading to more readable output of uglified code (size before
gzip could be smaller; size after gzip insignificantly larger).
- `shebang` (default `true`) -- preserve shebang `#!` in preamble (bash scripts)
- `width` (default 80) -- only takes effect when beautification is on, this
specifies an (orientative) line width that the beautifier will try to
obey. It refers to the width of the line text (excluding indentation).
It doesn't work very well currently, but it does make the code generated
by UglifyJS more readable.
- `wrap_iife` (default `false`) -- pass `true` to wrap immediately invoked
function expressions. See
[#640](https://github.com/mishoo/UglifyJS2/issues/640) for more details.
# Miscellaneous
### Keeping copyright notices or other comments
You can pass `--comments` to retain certain comments in the output. By
default it will keep JSDoc-style comments that contain "@preserve",
"@license" or "@cc_on" (conditional compilation for IE). You can pass
`--comments all` to keep all the comments, or a valid JavaScript regexp to
keep only comments that match this regexp. For example `--comments /^!/`
will keep comments like `/*! Copyright Notice */`.
Note, however, that there might be situations where comments are lost. For
example:
```javascript
function f() {
/** @preserve Foo Bar */
function g() {
// this function is never called
}
return something();
}
```
Even though it has "@preserve", the comment will be lost because the inner
function `g` (which is the AST node to which the comment is attached to) is
discarded by the compressor as not referenced.
The safest comments where to place copyright information (or other info that
needs to be kept in the output) are comments attached to toplevel nodes.
### The `unsafe` `compress` option
It enables some transformations that *might* break code logic in certain 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 contrived cases, but should be fine for most code. You might want to try it
@@ -439,7 +838,7 @@ scope). For example if you pass `--define DEBUG=false` then, coupled with
dead code removal UglifyJS will discard the following from the output: dead code removal UglifyJS will discard the following from the output:
```javascript ```javascript
if (DEBUG) { if (DEBUG) {
console.log("debug stuff"); console.log("debug stuff");
} }
``` ```
@@ -453,8 +852,8 @@ Another way of doing that is to declare your globals as constants in a
separate file and include it into the build. For example you can have a separate file and include it into the build. For example you can have a
`build/defines.js` file with the following: `build/defines.js` file with the following:
```javascript ```javascript
const DEBUG = false; var DEBUG = false;
const PRODUCTION = true; var PRODUCTION = true;
// etc. // etc.
``` ```
@@ -468,12 +867,13 @@ code as usual. The build will contain the `const` declarations if you use
them. If you are targeting < ES6 environments which does not support `const`, them. If you are targeting < ES6 environments which does not support `const`,
using `var` with `reduce_vars` (enabled by default) should suffice. using `var` with `reduce_vars` (enabled by default) should suffice.
#### Conditional compilation, API ### Conditional compilation API
You can also use conditional compilation via the programmatic API. With the difference that the You can also use conditional compilation via the programmatic API. With the difference that the
property name is `global_defs` and is a compressor property: property name is `global_defs` and is a compressor property:
```js ```javascript
uglifyJS.minify(fs.readFileSync("input.js", "utf8"), { var result = UglifyJS.minify(fs.readFileSync("input.js", "utf8"), {
compress: { compress: {
dead_code: true, dead_code: true,
global_defs: { global_defs: {
@@ -483,81 +883,72 @@ uglifyJS.minify(fs.readFileSync("input.js", "utf8"), {
}); });
``` ```
## Beautifier options To replace an identifier with an arbitrary non-constant expression it is
necessary to prefix the `global_defs` key with `"@"` to instruct UglifyJS
The code generator tries to output shortest code possible by default. In to parse the value as an expression:
case you want beautified output, pass `--beautify` (`-b`). Optionally you
can pass additional arguments that control the code output:
- `beautify` (default `true`) -- whether to actually beautify the output.
Passing `-b` will set this to true, but you might need to pass `-b` even
when you want to generate minified code, in order to specify additional
arguments, so you can use `-b beautify=false` to override it.
- `indent_level` (default 4)
- `indent_start` (default 0) -- prefix all lines by that many spaces
- `quote_keys` (default `false`) -- pass `true` to quote all keys in literal
objects
- `space_colon` (default `true`) -- insert a space after the colon signs
- `ascii_only` (default `false`) -- escape Unicode characters in strings and
regexps (affects directives with non-ascii characters becoming invalid)
- `inline_script` (default `false`) -- escape the slash in occurrences of
`</script` in strings
- `width` (default 80) -- only takes effect when beautification is on, this
specifies an (orientative) line width that the beautifier will try to
obey. It refers to the width of the line text (excluding indentation).
It doesn't work very well currently, but it does make the code generated
by UglifyJS more readable.
- `max_line_len` (default 32000) -- maximum line length (for uglified code)
- `bracketize` (default `false`) -- always insert brackets in `if`, `for`,
`do`, `while` or `with` statements, even if their body is a single
statement.
- `semicolons` (default `true`) -- separate statements with semicolons. If
you pass `false` then whenever possible we will use a newline instead of a
semicolon, leading to more readable output of uglified code (size before
gzip could be smaller; size after gzip insignificantly larger).
- `preamble` (default `null`) -- when passed it must be a string and
it will be prepended to the output literally. The source map will
adjust for this text. Can be used to insert a comment containing
licensing information, for example.
- `quote_style` (default `0`) -- preferred quote style for strings (affects
quoted property names and directives as well):
- `0` -- prefers double quotes, switches to single quotes when there are
more double quotes in the string itself.
- `1` -- always use single quotes
- `2` -- always use double quotes
- `3` -- always use the original quotes
- `keep_quoted_props` (default `false`) -- when turned on, prevents stripping
quotes from property names in object literals.
### Keeping copyright notices or other comments
You can pass `--comments` to retain certain comments in the output. By
default it will keep JSDoc-style comments that contain "@preserve",
"@license" or "@cc_on" (conditional compilation for IE). You can pass
`--comments all` to keep all the comments, or a valid JavaScript regexp to
keep only comments that match this regexp. For example `--comments /^!/`
will keep comments like `/*! Copyright Notice */`.
Note, however, that there might be situations where comments are lost. For
example:
```javascript ```javascript
function f() { UglifyJS.minify("alert('hello');", {
/** @preserve Foo Bar */ compress: {
function g() { global_defs: {
// this function is never called "@alert": "console.log"
} }
return something(); }
} }).code;
// returns: 'console.log("hello");'
``` ```
Even though it has "@preserve", the comment will be lost because the inner Otherwise it would be replaced as string literal:
function `g` (which is the AST node to which the comment is attached to) is ```javascript
discarded by the compressor as not referenced. UglifyJS.minify("alert('hello');", {
compress: {
global_defs: {
"alert": "console.log"
}
}
}).code;
// returns: '"console.log"("hello");'
```
The safest comments where to place copyright information (or other info that ### Using native Uglify AST with `minify()`
needs to be kept in the output) are comments attached to toplevel nodes. ```javascript
// example: parse only, produce native Uglify AST
## Support for the SpiderMonkey AST var result = UglifyJS.minify(code, {
parse: {},
compress: false,
mangle: false,
output: {
ast: true,
code: false // optional - faster if false
}
});
// result.ast contains native Uglify AST
```
```javascript
// example: accept native Uglify AST input and then compress and mangle
// to produce both code and native AST.
var result = UglifyJS.minify(ast, {
compress: {},
mangle: {},
output: {
ast: true,
code: true // optional - faster if false
}
});
// result.ast contains native Uglify AST
// result.code contains the minified code in string form.
```
### Working with Uglify AST
Transversal and transformation of the native AST can be performed through
[`TreeWalker`](http://lisperator.net/uglifyjs/walk) and
[`TreeTransformer`](http://lisperator.net/uglifyjs/transform) respectively.
### ESTree / SpiderMonkey AST
UglifyJS has its own abstract syntax tree format; for UglifyJS has its own abstract syntax tree format; for
[practical reasons](http://lisperator.net/blog/uglifyjs-why-not-switching-to-spidermonkey-ast/) [practical reasons](http://lisperator.net/blog/uglifyjs-why-not-switching-to-spidermonkey-ast/)
@@ -585,141 +976,5 @@ Acorn is really fast (e.g. 250ms instead of 380ms on some 650K code), but
converting the SpiderMonkey tree that Acorn produces takes another 150ms so converting the SpiderMonkey tree that Acorn produces takes another 150ms so
in total it's a bit more than just using UglifyJS's own parser. in total it's a bit more than just using UglifyJS's own parser.
API Reference
-------------
Assuming installation via NPM, you can load UglifyJS in your application
like this:
```javascript
var UglifyJS = require("uglify-js");
```
There is a single toplevel function, `minify(files, options)`, which will
performs all the steps in a configurable manner.
Example:
```javascript
var result = UglifyJS.minify("var b = function() {};");
console.log(result.code); // minified output
console.log(result.error); // runtime error
```
You can also compress multiple files:
```javascript
var result = UglifyJS.minify({
"file1.js": "var a = function() {};",
"file2.js": "var b = function() {};"
});
console.log(result.code);
```
To generate a source map:
```javascript
var result = UglifyJS.minify({"file1.js": "var a = function() {};"}, {
sourceMap: {
filename: "out.js",
url: "out.js.map"
}
});
console.log(result.code); // minified output
console.log(result.map); // source map
```
Note that the source map is not saved in a file, it's just returned in
`result.map`. The value passed for `sourceMap.url` is only used to set
`//# sourceMappingURL=out.js.map` in `result.code`. The value of
`filename` is only used to set `file` attribute (see [the spec][sm-spec])
in source map file.
You can set option `sourceMap.url` to be `"inline"` and source map will
be appended to code.
You can also specify sourceRoot property to be included in source map:
```javascript
var result = UglifyJS.minify({"file1.js": "var a = function() {};"}, {
sourceMap: {
root: "http://example.com/src",
url: "out.js.map"
}
});
```
If you're compressing compiled JavaScript and have a source map for it, you
can use `sourceMap.content`:
```javascript
var result = UglifyJS.minify({"compiled.js": "compiled code"}, {
sourceMap: {
content: "content from compiled.js.map",
url: "minified.js.map"
}
});
// same as before, it returns `code` and `map`
```
If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.url`.
Other options:
- `warnings` (default `false`) — pass `true` to display compressor warnings.
- `mangle` (default `true`) — pass `false` to skip mangling names, or pass
an object to specify mangling options (see below).
- `mangleProperties` (default `false`) — pass an object to specify custom
mangle property options.
- `output` (default `null`) — pass an object if you wish to specify
additional [output options](#beautifier-options). The defaults are optimized
for best compression.
- `compress` (default `{}`) — pass `false` to skip compressing entirely.
Pass an object to specify custom [compressor options](#compressor-options).
- `parse` (default {}) — pass an object if you wish to specify some
additional [parser options](#the-parser).
##### mangle
- `reserved` - pass an array of identifiers that should be excluded from mangling
- `toplevel` — mangle names declared in the toplevel scope (disabled by
default).
- `eval` — mangle names visible in scopes where eval or with are used
(disabled by default).
- `keep_fnames` -- default `false`. Pass `true` to not mangle
function names. Useful for code relying on `Function.prototype.name`.
See also: the `keep_fnames` [compress option](#compressor-options).
Examples:
```javascript
// test.js
var globalVar;
function funcName(firstLongName, anotherLongName)
{
var myVariable = firstLongName + anotherLongName;
}
```
```javascript
var code = fs.readFileSync("test.js", "utf8");
UglifyJS.minify(code).code;
// 'function funcName(a,n){}var globalVar;'
UglifyJS.minify(code, { mangle: { reserved: ['firstLongName'] } }).code;
// 'function funcName(firstLongName,a){}var globalVar;'
UglifyJS.minify(code, { mangle: { toplevel: true } }).code;
// 'function n(n,a){}var a;'
```
##### mangle.properties options
- `regex` — Pass a RegExp to only mangle certain names
- `keep_quoted` — Only mangle unquoted property names
- `debug` — Mangle names with the original name still present. Defaults to `false`.
Pass an empty string to enable, or a non-empty string to set the suffix.
[acorn]: https://github.com/ternjs/acorn [acorn]: https://github.com/ternjs/acorn
[sm-spec]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k [sm-spec]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k

View File

@@ -21,10 +21,20 @@ var options = {
compress: false, compress: false,
mangle: false mangle: false
}; };
program._name = info.name; program.version(info.name + " " + info.version);
program.version(info.version);
program.parseArgv = program.parse; program.parseArgv = program.parse;
program.parse = undefined; program.parse = undefined;
if (process.argv.indexOf("ast") >= 0) program.helpInformation = UglifyJS.describe_ast;
else if (process.argv.indexOf("options") >= 0) program.helpInformation = function() {
var text = [];
var options = UglifyJS.default_options();
for (var option in options) {
text.push("--" + (option == "output" ? "beautify" : option == "sourceMap" ? "source-map" : option) + " options:");
text.push(format_object(options[option]));
text.push("");
}
return text.join("\n");
};
program.option("-p, --parse <options>", "Specify parser options.", parse_js("parse", true)); program.option("-p, --parse <options>", "Specify parser options.", parse_js("parse", true));
program.option("-c, --compress [options]", "Enable compressor/specify compressor options.", parse_js("compress", true)); program.option("-c, --compress [options]", "Enable compressor/specify compressor options.", parse_js("compress", true));
program.option("-m, --mangle [options]", "Mangle names/specify mangler options.", parse_js("mangle", true)); program.option("-m, --mangle [options]", "Mangle names/specify mangler options.", parse_js("mangle", true));
@@ -39,7 +49,7 @@ program.option("--keep-fnames", "Do not mangle/drop function names. Useful for c
program.option("--name-cache <file>", "File to hold mangled name mappings."); program.option("--name-cache <file>", "File to hold mangled name mappings.");
program.option("--self", "Build UglifyJS as a library (implies --wrap UglifyJS)"); program.option("--self", "Build UglifyJS as a library (implies --wrap UglifyJS)");
program.option("--source-map [options]", "Enable source map/specify source map options.", parse_source_map()); program.option("--source-map [options]", "Enable source map/specify source map options.", parse_source_map());
program.option("--stats", "Display operations run time on STDERR.") program.option("--timings", "Display operations run time on STDERR.")
program.option("--toplevel", "Compress and/or mangle variables in toplevel scope."); program.option("--toplevel", "Compress and/or mangle variables in toplevel scope.");
program.option("--verbose", "Print diagnostic messages."); program.option("--verbose", "Print diagnostic messages.");
program.option("--warn", "Print warning messages."); program.option("--warn", "Print warning messages.");
@@ -115,10 +125,10 @@ if (program.output == "ast") {
}; };
} }
if (program.parse) { if (program.parse) {
if (program.parse.acorn || program.parse.spidermonkey) { if (!program.parse.acorn && !program.parse.spidermonkey) {
if (program.sourceMap) fatal("ERROR: inline source map only works with built-in parser");
} else {
options.parse = program.parse; options.parse = program.parse;
} else if (program.sourceMap && program.sourceMap.content == "inline") {
fatal("ERROR: inline source map only works with built-in parser");
} }
} }
var convert_path = function(name) { var convert_path = function(name) {
@@ -140,7 +150,7 @@ if (program.verbose) {
} }
if (program.self) { if (program.self) {
if (program.args.length) { if (program.args.length) {
console.error("WARN: Ignoring input files since --self was passed"); print_error("WARN: Ignoring input files since --self was passed");
} }
if (!options.wrap) options.wrap = "UglifyJS"; if (!options.wrap) options.wrap = "UglifyJS";
simple_glob(UglifyJS.FILES).forEach(function(name) { simple_glob(UglifyJS.FILES).forEach(function(name) {
@@ -170,9 +180,9 @@ function convert_ast(fn) {
function run() { function run() {
UglifyJS.AST_Node.warn_function = function(msg) { UglifyJS.AST_Node.warn_function = function(msg) {
console.error("WARN:", msg); print_error("WARN: " + msg);
}; };
if (program.stats) program.stats = Date.now(); if (program.timings) options.timings = true;
try { try {
if (program.parse) { if (program.parse) {
if (program.parse.acorn) { if (program.parse.acorn) {
@@ -199,7 +209,7 @@ function run() {
if (result.error) { if (result.error) {
var ex = result.error; var ex = result.error;
if (ex.name == "SyntaxError") { if (ex.name == "SyntaxError") {
console.error("Parse error at " + ex.filename + ":" + ex.line + "," + ex.col); print_error("Parse error at " + ex.filename + ":" + ex.line + "," + ex.col);
var col = ex.col; var col = ex.col;
var lines = files[ex.filename].split(/\r?\n/); var lines = files[ex.filename].split(/\r?\n/);
var line = lines[ex.line - 1]; var line = lines[ex.line - 1];
@@ -208,21 +218,22 @@ function run() {
col = line.length; col = line.length;
} }
if (line) { if (line) {
if (col > 40) { var limit = 70;
line = line.slice(col - 40); if (col > limit) {
col = 40; line = line.slice(col - limit);
col = limit;
} }
console.error(line.slice(0, 80)); print_error(line.slice(0, 80));
console.error(line.slice(0, col).replace(/\S/g, " ") + "^"); print_error(line.slice(0, col).replace(/\S/g, " ") + "^");
} }
} }
if (ex.defs) { if (ex.defs) {
console.error("Supported options:"); print_error("Supported options:");
console.error(ex.defs); print_error(format_object(ex.defs));
} }
fatal(ex); fatal(ex);
} else if (program.output == "ast") { } else if (program.output == "ast") {
console.log(JSON.stringify(result.ast, function(key, value) { print(JSON.stringify(result.ast, function(key, value) {
if (skip_key(key)) return; if (skip_key(key)) return;
if (value instanceof UglifyJS.AST_Token) return; if (value instanceof UglifyJS.AST_Token) return;
if (value instanceof UglifyJS.Dictionary) return; if (value instanceof UglifyJS.Dictionary) return;
@@ -238,7 +249,7 @@ function run() {
return value; return value;
}, 2)); }, 2));
} else if (program.output == "spidermonkey") { } else if (program.output == "spidermonkey") {
console.log(JSON.stringify(UglifyJS.minify(result.code, { print(JSON.stringify(UglifyJS.minify(result.code, {
compress: false, compress: false,
mangle: false, mangle: false,
output: { output: {
@@ -252,19 +263,21 @@ function run() {
fs.writeFileSync(program.output + ".map", result.map); fs.writeFileSync(program.output + ".map", result.map);
} }
} else { } else {
console.log(result.code); print(result.code);
} }
if (program.nameCache) { if (program.nameCache) {
fs.writeFileSync(program.nameCache, JSON.stringify(cache, function(key, value) { fs.writeFileSync(program.nameCache, JSON.stringify(cache, function(key, value) {
return value instanceof UglifyJS.Dictionary ? value.toObject() : value; return value instanceof UglifyJS.Dictionary ? value.toObject() : value;
})); }));
} }
if (program.stats) console.error("Elapsed:", Date.now() - program.stats); if (result.timings) for (var phase in result.timings) {
print_error("- " + phase + ": " + result.timings[phase].toFixed(3) + "s");
}
} }
function fatal(message) { function fatal(message) {
if (message instanceof Error) message = message.stack.replace(/^\S*?Error:/, "ERROR:") if (message instanceof Error) message = message.stack.replace(/^\S*?Error:/, "ERROR:")
console.error(message); print_error(message);
process.exit(1); process.exit(1);
} }
@@ -349,7 +362,7 @@ function parse_js(flag, constants) {
} }
})); }));
} catch(ex) { } catch(ex) {
fatal("Error parsing arguments for '" + flag + "': " + value); options[value] = null;
} }
return options; return options;
} }
@@ -358,10 +371,10 @@ function parse_js(flag, constants) {
function parse_source_map() { function parse_source_map() {
var parse = parse_js("sourceMap", true); var parse = parse_js("sourceMap", true);
return function(value, options) { return function(value, options) {
var hasContent = options && options.sourceMap && "content" in options.sourceMap; var hasContent = options && "content" in options;
var settings = parse(value, options); var settings = parse(value, options);
if (!hasContent && settings.content && settings.content != "inline") { if (!hasContent && settings.content && settings.content != "inline") {
console.error("INFO: Using input source map:", settings.content); print_error("INFO: Using input source map: " + settings.content);
settings.content = read_file(settings.content, settings.content); settings.content = read_file(settings.content, settings.content);
} }
return settings; return settings;
@@ -383,3 +396,25 @@ function to_cache(key) {
function skip_key(key) { function skip_key(key) {
return skip_keys.indexOf(key) >= 0; return skip_keys.indexOf(key) >= 0;
} }
function format_object(obj) {
var lines = [];
var padding = "";
Object.keys(obj).map(function(name) {
if (padding.length < name.length) padding = Array(name.length + 1).join(" ");
return [ name, JSON.stringify(obj[name]) ];
}).forEach(function(tokens) {
lines.push(" " + tokens[0] + padding.slice(tokens[0].length - 2) + tokens[1]);
});
return lines.join("\n");
}
function print_error(msg) {
process.stderr.write(msg);
process.stderr.write("\n");
}
function print(txt) {
process.stdout.write(txt);
process.stdout.write("\n");
}

View File

@@ -63,6 +63,7 @@ function Compressor(options, false_by_default) {
hoist_vars : false, hoist_vars : false,
ie8 : false, ie8 : false,
if_return : !false_by_default, if_return : !false_by_default,
inline : !false_by_default,
join_vars : !false_by_default, join_vars : !false_by_default,
keep_fargs : true, keep_fargs : true,
keep_fnames : false, keep_fnames : false,
@@ -81,11 +82,24 @@ function Compressor(options, false_by_default) {
toplevel : !!(options && options["top_retain"]), toplevel : !!(options && options["top_retain"]),
unsafe : false, unsafe : false,
unsafe_comps : false, unsafe_comps : false,
unsafe_Func : false,
unsafe_math : false, unsafe_math : false,
unsafe_proto : false, unsafe_proto : false,
unsafe_regexp : false,
unused : !false_by_default, unused : !false_by_default,
warnings : false, warnings : false,
}, true); }, true);
var global_defs = this.options["global_defs"];
if (typeof global_defs == "object") for (var key in global_defs) {
if (/^@/.test(key) && HOP(global_defs, key)) {
var ast = parse(global_defs[key]);
if (ast.body.length == 1 && ast.body[0] instanceof AST_SimpleStatement) {
global_defs[key.slice(1)] = ast.body[0].body;
} else throw new Error(string_template("Can't handle expression: {value}", {
value: global_defs[key]
}));
}
}
var pure_funcs = this.options["pure_funcs"]; var pure_funcs = this.options["pure_funcs"];
if (typeof pure_funcs == "function") { if (typeof pure_funcs == "function") {
this.pure_funcs = pure_funcs; this.pure_funcs = pure_funcs;
@@ -912,13 +926,52 @@ merge(Compressor.prototype, {
// step. nevertheless, it's good to check. // step. nevertheless, it's good to check.
continue loop; continue loop;
case stat instanceof AST_If: case stat instanceof AST_If:
var ab = aborts(stat.body);
if (can_merge_flow(ab)) {
if (ab.label) {
remove(ab.label.thedef.references, ab);
}
CHANGED = true;
var funs = extract_functions_from_statement_array(ret);
var body = as_statement_array_with_return(stat.body, ab);
stat = stat.clone();
stat.condition = stat.condition.negate(compressor);
stat.body = make_node(AST_BlockStatement, stat, {
body: as_statement_array(stat.alternative).concat(ret)
});
stat.alternative = make_node(AST_BlockStatement, stat, {
body: body
});
ret = [ stat.transform(compressor) ].concat(funs);
continue loop;
}
var ab = aborts(stat.alternative);
if (can_merge_flow(ab)) {
if (ab.label) {
remove(ab.label.thedef.references, ab);
}
CHANGED = true;
var funs = extract_functions_from_statement_array(ret);
stat = stat.clone();
stat.body = make_node(AST_BlockStatement, stat.body, {
body: as_statement_array(stat.body).concat(ret)
});
var body = as_statement_array_with_return(stat.alternative, ab);
stat.alternative = make_node(AST_BlockStatement, stat.alternative, {
body: body
});
ret = [ stat.transform(compressor) ].concat(funs);
continue loop;
}
if (stat.body instanceof AST_Return) { if (stat.body instanceof AST_Return) {
var value = stat.body.value;
//--- //---
// pretty silly case, but: // pretty silly case, but:
// if (foo()) return; return; ==> foo(); return; // if (foo()) return; return; ==> foo(); return;
if (((in_lambda && ret.length == 0) if ((in_lambda && ret.length == 0 || ret[0] instanceof AST_Return && !ret[0].value)
|| (ret[0] instanceof AST_Return && !ret[0].value)) && !value && !stat.alternative) {
&& !stat.body.value && !stat.alternative) {
CHANGED = true; CHANGED = true;
var cond = make_node(AST_SimpleStatement, stat.condition, { var cond = make_node(AST_SimpleStatement, stat.condition, {
body: stat.condition body: stat.condition
@@ -928,7 +981,7 @@ merge(Compressor.prototype, {
} }
//--- //---
// if (foo()) return x; return y; ==> return foo() ? x : y; // if (foo()) return x; return y; ==> return foo() ? x : y;
if (ret[0] instanceof AST_Return && stat.body.value && ret[0].value && !stat.alternative) { if (ret[0] instanceof AST_Return && value && ret[0].value && !stat.alternative) {
CHANGED = true; CHANGED = true;
stat = stat.clone(); stat = stat.clone();
stat.alternative = ret[0]; stat.alternative = ret[0];
@@ -938,7 +991,7 @@ merge(Compressor.prototype, {
//--- //---
// if (foo()) return x; [ return ; ] ==> return foo() ? x : undefined; // if (foo()) return x; [ return ; ] ==> return foo() ? x : undefined;
if (multiple_if_returns && (ret.length == 0 || ret[0] instanceof AST_Return) if (multiple_if_returns && (ret.length == 0 || ret[0] instanceof AST_Return)
&& stat.body.value && !stat.alternative && in_lambda) { && value && !stat.alternative && in_lambda) {
CHANGED = true; CHANGED = true;
stat = stat.clone(); stat = stat.clone();
stat.alternative = ret[0] || make_node(AST_Return, stat, { stat.alternative = ret[0] || make_node(AST_Return, stat, {
@@ -947,22 +1000,6 @@ merge(Compressor.prototype, {
ret[0] = stat.transform(compressor); ret[0] = stat.transform(compressor);
continue loop; continue loop;
} }
//---
// if (foo()) return; [ else x... ]; y... ==> if (!foo()) { x...; y... }
if (!stat.body.value && in_lambda) {
CHANGED = true;
stat = stat.clone();
stat.condition = stat.condition.negate(compressor);
var body = as_statement_array(stat.alternative).concat(ret);
var funs = extract_functions_from_statement_array(body);
stat.body = make_node(AST_BlockStatement, stat, {
body: body
});
stat.alternative = null;
ret = funs.concat([ stat.transform(compressor) ]);
continue loop;
}
//--- //---
// if (a) return b; if (c) return d; e; ==> return a ? b : c ? d : void e; // if (a) return b; if (c) return d; e; ==> return a ? b : c ? d : void e;
// //
@@ -982,48 +1019,6 @@ merge(Compressor.prototype, {
} }
} }
var ab = aborts(stat.body);
var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab) : null;
if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda)
|| (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);
}
CHANGED = true;
var body = as_statement_array(stat.body).slice(0, -1);
stat = stat.clone();
stat.condition = stat.condition.negate(compressor);
stat.body = make_node(AST_BlockStatement, stat, {
body: as_statement_array(stat.alternative).concat(ret)
});
stat.alternative = make_node(AST_BlockStatement, stat, {
body: body
});
ret = [ stat.transform(compressor) ];
continue loop;
}
var ab = aborts(stat.alternative);
var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab) : null;
if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda)
|| (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);
}
CHANGED = true;
stat = stat.clone();
stat.body = make_node(AST_BlockStatement, stat.body, {
body: as_statement_array(stat.body).concat(ret)
});
stat.alternative = make_node(AST_BlockStatement, stat.alternative, {
body: as_statement_array(stat.alternative).slice(0, -1)
});
ret = [ stat.transform(compressor) ];
continue loop;
}
ret.unshift(stat); ret.unshift(stat);
break; break;
default: default:
@@ -1043,6 +1038,28 @@ merge(Compressor.prototype, {
} }
return false; return false;
} }
function is_return_void(value) {
return !value || value instanceof AST_UnaryPrefix && value.operator == "void";
}
function can_merge_flow(ab) {
if (!ab) return false;
var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab) : null;
return ab instanceof AST_Return && in_lambda && is_return_void(ab.value)
|| ab instanceof AST_Continue && self === loop_body(lct)
|| ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct;
}
function as_statement_array_with_return(node, ab) {
var body = as_statement_array(node).slice(0, -1);
if (ab.value) {
body.push(make_node(AST_SimpleStatement, ab.value, {
body: ab.value.expression
}));
}
return body;
}
}; };
function eliminate_dead_code(statements, compressor) { function eliminate_dead_code(statements, compressor) {
@@ -1209,7 +1226,7 @@ merge(Compressor.prototype, {
target.push(node); target.push(node);
return true; return true;
} }
if (node instanceof AST_Defun) { if (node instanceof AST_Defun && (node === stat || !compressor.has_directive("use strict"))) {
target.push(node); target.push(node);
return true; return true;
} }
@@ -1402,7 +1419,7 @@ merge(Compressor.prototype, {
}); });
if (value && typeof value == "object") { if (value && typeof value == "object") {
var props = []; var props = [];
for (var key in value) { for (var key in value) if (HOP(value, key)) {
props.push(make_node(AST_ObjectKeyVal, orig, { props.push(make_node(AST_ObjectKeyVal, orig, {
key: key, key: key,
value: to_node(value[key], orig) value: to_node(value[key], orig)
@@ -1649,6 +1666,63 @@ merge(Compressor.prototype, {
} }
throw def; throw def;
}); });
var object_fns = [
'constructor',
'toString',
'valueOf',
];
var native_fns = {
Array: makePredicate([
'indexOf',
'join',
'lastIndexOf',
'slice',
].concat(object_fns)),
Boolean: makePredicate(object_fns),
Number: makePredicate([
'toExponential',
'toFixed',
'toPrecision',
].concat(object_fns)),
RegExp: makePredicate([
'test',
].concat(object_fns)),
String: makePredicate([
'charAt',
'charCodeAt',
'concat',
'indexOf',
'italics',
'lastIndexOf',
'match',
'replace',
'search',
'slice',
'split',
'substr',
'substring',
'trim',
].concat(object_fns)),
};
def(AST_Call, function(compressor){
var exp = this.expression;
if (compressor.option("unsafe") && exp instanceof AST_PropAccess) {
var key = exp.property;
if (key instanceof AST_Node) {
key = ev(key, compressor);
}
var val = ev(exp.expression, compressor);
if ((val && native_fns[val.constructor.name] || return_false)(key)) {
return val[key].apply(val, this.args.map(function(arg) {
return ev(arg, compressor);
}));
}
}
throw def;
});
def(AST_New, function(compressor){
throw def;
});
})(function(node, func){ })(function(node, func){
node.DEFMETHOD("_eval", func); node.DEFMETHOD("_eval", func);
}); });
@@ -2238,11 +2312,12 @@ merge(Compressor.prototype, {
dirs.push(node); dirs.push(node);
return make_node(AST_EmptyStatement, node); return make_node(AST_EmptyStatement, node);
} }
if (node instanceof AST_Defun && hoist_funs) { if (hoist_funs && node instanceof AST_Defun
&& (tt.parent() === self || !compressor.has_directive("use strict"))) {
hoisted.push(node); hoisted.push(node);
return make_node(AST_EmptyStatement, node); return make_node(AST_EmptyStatement, node);
} }
if (node instanceof AST_Var && hoist_vars) { if (hoist_vars && node instanceof AST_Var) {
node.definitions.forEach(function(def){ node.definitions.forEach(function(def){
vars.set(def.name.name, def); vars.set(def.name.name, def);
++vars_found; ++vars_found;
@@ -2869,24 +2944,9 @@ merge(Compressor.prototype, {
OPT(AST_Call, function(self, compressor){ OPT(AST_Call, function(self, compressor){
var exp = self.expression; var exp = self.expression;
if (compressor.option("reduce_vars") if (compressor.option("reduce_vars") && exp instanceof AST_SymbolRef) {
&& exp instanceof AST_SymbolRef) {
var def = exp.definition();
var fixed = exp.fixed_value(); var fixed = exp.fixed_value();
if (fixed instanceof AST_Defun) { if (fixed instanceof AST_Function) exp = fixed;
def.fixed = fixed = make_node(AST_Function, fixed, fixed).clone(true);
}
if (fixed instanceof AST_Function) {
exp = fixed;
if (compressor.option("unused")
&& def.references.length == 1
&& !(def.scope.uses_arguments
&& def.orig[0] instanceof AST_SymbolFunarg)
&& !def.scope.uses_eval
&& compressor.find_parent(AST_Scope) === def.scope) {
self.expression = exp;
}
}
} }
if (compressor.option("unused") if (compressor.option("unused")
&& exp instanceof AST_Function && exp instanceof AST_Function
@@ -2957,63 +3017,6 @@ merge(Compressor.prototype, {
operator: "!" operator: "!"
}).optimize(compressor); }).optimize(compressor);
break; break;
case "Function":
// new Function() => function(){}
if (self.args.length == 0) return make_node(AST_Function, self, {
argnames: [],
body: []
});
if (all(self.args, function(x){ return x instanceof AST_String })) {
// quite a corner-case, but we can handle it:
// https://github.com/mishoo/UglifyJS2/issues/203
// if the code argument is a constant, then we can minify it.
try {
var code = "(function(" + self.args.slice(0, -1).map(function(arg){
return arg.value;
}).join(",") + "){" + self.args[self.args.length - 1].value + "})()";
var ast = parse(code);
var mangle = { ie8: compressor.option("ie8") };
ast.figure_out_scope(mangle);
var comp = new Compressor(compressor.options);
ast = ast.transform(comp);
ast.figure_out_scope(mangle);
ast.mangle_names();
var fun;
try {
ast.walk(new TreeWalker(function(node){
if (node instanceof AST_Lambda) {
fun = node;
throw ast;
}
}));
} catch(ex) {
if (ex !== ast) throw ex;
};
if (!fun) return self;
var args = fun.argnames.map(function(arg, i){
return make_node(AST_String, self.args[i], {
value: arg.print_to_string()
});
});
var code = OutputStream();
AST_BlockStatement.prototype._codegen.call(fun, fun, code);
code = code.toString().replace(/^\{|\}$/g, "");
args.push(make_node(AST_String, self.args[self.args.length - 1], {
value: code
}));
self.args = args;
return self;
} catch(ex) {
if (ex instanceof JS_Parse_Error) {
compressor.warn("Error parsing code passed to new Function [{file}:{line},{col}]", self.args[self.args.length - 1].start);
compressor.warn(ex.toString());
} else {
console.log(ex);
throw ex;
}
}
}
break;
} }
} }
else if (exp instanceof AST_Dot && exp.property == "toString" && self.args.length == 0) { else if (exp instanceof AST_Dot && exp.property == "toString" && self.args.length == 0) {
@@ -3096,14 +3099,128 @@ merge(Compressor.prototype, {
} }
} }
} }
if (compressor.option("unsafe_Func")
&& exp instanceof AST_SymbolRef
&& exp.undeclared()
&& exp.name == "Function") {
// new Function() => function(){}
if (self.args.length == 0) return make_node(AST_Function, self, {
argnames: [],
body: []
});
if (all(self.args, function(x) {
return x instanceof AST_String;
})) {
// quite a corner-case, but we can handle it:
// https://github.com/mishoo/UglifyJS2/issues/203
// if the code argument is a constant, then we can minify it.
try {
var code = "NaN(function(" + self.args.slice(0, -1).map(function(arg) {
return arg.value;
}).join(",") + "){" + self.args[self.args.length - 1].value + "})";
var ast = parse(code);
var mangle = { ie8: compressor.option("ie8") };
ast.figure_out_scope(mangle);
var comp = new Compressor(compressor.options);
ast = ast.transform(comp);
ast.figure_out_scope(mangle);
ast.mangle_names();
var fun;
ast.walk(new TreeWalker(function(node) {
if (fun) return true;
if (node instanceof AST_Lambda) {
fun = node;
return true;
}
}));
var args = fun.argnames.map(function(arg, i) {
return make_node(AST_String, self.args[i], {
value: arg.print_to_string()
});
});
var code = OutputStream();
AST_BlockStatement.prototype._codegen.call(fun, fun, code);
code = code.toString().replace(/^\{|\}$/g, "");
args.push(make_node(AST_String, self.args[self.args.length - 1], {
value: code
}));
self.args = args;
return self;
} catch (ex) {
if (ex instanceof JS_Parse_Error) {
compressor.warn("Error parsing code passed to new Function [{file}:{line},{col}]", self.args[self.args.length - 1].start);
compressor.warn(ex.toString());
} else {
throw ex;
}
}
}
}
if (exp instanceof AST_Function) { if (exp instanceof AST_Function) {
if (exp.body[0] instanceof AST_Return) { var stat = exp.body[0];
var value = exp.body[0].value; if (compressor.option("inline") && stat instanceof AST_Return) {
var value = stat && stat.value;
if (!value || value.is_constant_expression()) { if (!value || value.is_constant_expression()) {
var args = self.args.concat(value || make_node(AST_Undefined, self)); var args = self.args.concat(value || make_node(AST_Undefined, self));
return make_sequence(self, args).transform(compressor); return make_sequence(self, args).transform(compressor);
} }
} }
if (compressor.option("inline")
&& !exp.name
&& exp.body.length == 1
&& !exp.uses_arguments
&& !exp.uses_eval
&& !self.has_pure_annotation(compressor)) {
var value;
if (stat instanceof AST_Return) {
value = stat.value.clone(true);
} else if (stat instanceof AST_SimpleStatement) {
value = make_node(AST_UnaryPrefix, stat, {
operator: "void",
expression: stat.body.clone(true)
});
}
if (value) {
var fn = exp.clone();
fn.argnames = [];
fn.body = [];
if (exp.argnames.length > 0) {
fn.body.push(make_node(AST_Var, self, {
definitions: exp.argnames.map(function(sym, i) {
return make_node(AST_VarDef, sym, {
name: sym,
value: self.args[i] || make_node(AST_Undefined, self)
});
})
}));
}
if (self.args.length > exp.argnames.length) {
fn.body.push(make_node(AST_SimpleStatement, self, {
body: make_sequence(self, self.args.slice(exp.argnames.length))
}));
}
fn.body.push(make_node(AST_Return, self, {
value: value
}));
var body = fn.transform(compressor).body;
if (body.length == 0) return make_node(AST_Undefined, self);
if (body.length == 1 && body[0] instanceof AST_Return) {
value = body[0].value;
if (!value) return make_node(AST_Undefined, self);
value.walk(new TreeWalker(function(node) {
if (value === self) return true;
if (node instanceof AST_SymbolRef && exp.variables.has(node.name)) {
value = self;
return true;
}
}));
if (value !== self) value = best_of(compressor, value, self);
} else {
value = self;
}
if (value !== self) return value;
}
}
if (compressor.option("side_effects") && all(exp.body, is_empty)) { if (compressor.option("side_effects") && all(exp.body, is_empty)) {
var args = self.args.concat(make_node(AST_Undefined, self)); var args = self.args.concat(make_node(AST_Undefined, self));
return make_sequence(self, args).transform(compressor); return make_sequence(self, args).transform(compressor);
@@ -3127,6 +3244,11 @@ merge(Compressor.prototype, {
&& is_iife_call(self)) { && is_iife_call(self)) {
return self.negate(compressor, true); return self.negate(compressor, true);
} }
var ev = self.evaluate(compressor);
if (ev !== self) {
ev = make_node_from_constant(ev, self).optimize(compressor);
return best_of(compressor, ev, self);
}
return self; return self;
}); });
@@ -3201,6 +3323,7 @@ merge(Compressor.prototype, {
continue; continue;
} }
var parent = null, field; var parent = null, field;
expressions[j] = cdr = cdr.clone();
while (true) { while (true) {
if (cdr.equivalent_to(left)) { if (cdr.equivalent_to(left)) {
var car = expressions[i]; var car = expressions[i];
@@ -3237,7 +3360,7 @@ merge(Compressor.prototype, {
break; break;
} }
parent = cdr; parent = cdr;
cdr = cdr[field]; cdr = cdr[field] = cdr[field].clone();
} }
} }
end = i; end = i;
@@ -3757,15 +3880,25 @@ merge(Compressor.prototype, {
return make_node(AST_Infinity, self).optimize(compressor); return make_node(AST_Infinity, self).optimize(compressor);
} }
} }
if (compressor.option("evaluate") if (compressor.option("reduce_vars")
&& compressor.option("reduce_vars")
&& is_lhs(self, compressor.parent()) !== self) { && is_lhs(self, compressor.parent()) !== self) {
var d = self.definition(); var d = self.definition();
var fixed = self.fixed_value(); var fixed = self.fixed_value();
if (fixed) { if (fixed instanceof AST_Defun) {
d.fixed = fixed = make_node(AST_Function, fixed, fixed).clone(true);
}
if (compressor.option("unused")
&& fixed instanceof AST_Function
&& d.references.length == 1
&& !(d.scope.uses_arguments && d.orig[0] instanceof AST_SymbolFunarg)
&& !d.scope.uses_eval
&& compressor.find_parent(AST_Scope) === fixed.parent_scope) {
return fixed;
}
if (compressor.option("evaluate") && fixed) {
if (d.should_replace === undefined) { if (d.should_replace === undefined) {
var init = fixed.evaluate(compressor); var init = fixed.evaluate(compressor);
if (init !== fixed) { if (init !== fixed && (compressor.option("unsafe_regexp") || !(init instanceof RegExp))) {
init = make_node_from_constant(init, fixed); init = make_node_from_constant(init, fixed);
var value_length = init.optimize(compressor).print_to_string().length; var value_length = init.optimize(compressor).print_to_string().length;
var fn; var fn;

View File

@@ -30,9 +30,6 @@ function set_shorthand(name, options, keys) {
function minify(files, options) { function minify(files, options) {
var warn_function = AST_Node.warn_function; var warn_function = AST_Node.warn_function;
try { try {
if (typeof files == "string") {
files = [ files ];
}
options = defaults(options, { options = defaults(options, {
compress: {}, compress: {},
ie8: false, ie8: false,
@@ -41,10 +38,14 @@ function minify(files, options) {
output: {}, output: {},
parse: {}, parse: {},
sourceMap: false, sourceMap: false,
timings: false,
toplevel: false, toplevel: false,
warnings: false, warnings: false,
wrap: false, wrap: false,
}, true); }, true);
var timings = options.timings && {
start: Date.now()
};
set_shorthand("ie8", options, [ "compress", "mangle", "output" ]); set_shorthand("ie8", options, [ "compress", "mangle", "output" ]);
set_shorthand("keep_fnames", options, [ "compress", "mangle" ]); set_shorthand("keep_fnames", options, [ "compress", "mangle" ]);
set_shorthand("toplevel", options, [ "compress", "mangle" ]); set_shorthand("toplevel", options, [ "compress", "mangle" ]);
@@ -75,13 +76,17 @@ function minify(files, options) {
warnings.push(warning); warnings.push(warning);
}; };
} }
if (timings) timings.parse = Date.now();
var toplevel; var toplevel;
if (files instanceof AST_Toplevel) { if (files instanceof AST_Toplevel) {
toplevel = files; toplevel = files;
} else { } else {
if (typeof files == "string") {
files = [ files ];
}
options.parse = options.parse || {}; options.parse = options.parse || {};
options.parse.toplevel = null; options.parse.toplevel = null;
for (var name in files) { for (var name in files) if (HOP(files, name)) {
options.parse.filename = name; options.parse.filename = name;
options.parse.toplevel = parse(files[name], options.parse); options.parse.toplevel = parse(files[name], options.parse);
if (options.sourceMap && options.sourceMap.content == "inline") { if (options.sourceMap && options.sourceMap.content == "inline") {
@@ -95,19 +100,23 @@ function minify(files, options) {
if (options.wrap) { if (options.wrap) {
toplevel = toplevel.wrap_commonjs(options.wrap); toplevel = toplevel.wrap_commonjs(options.wrap);
} }
if (options.compress) { if (timings) timings.scope1 = Date.now();
toplevel.figure_out_scope(options.mangle); if (options.compress) toplevel.figure_out_scope(options.mangle);
toplevel = new Compressor(options.compress).compress(toplevel); if (timings) timings.compress = Date.now();
} if (options.compress) toplevel = new Compressor(options.compress).compress(toplevel);
if (timings) timings.scope2 = Date.now();
if (options.mangle) toplevel.figure_out_scope(options.mangle);
if (timings) timings.mangle = Date.now();
if (options.mangle) { if (options.mangle) {
toplevel.figure_out_scope(options.mangle);
base54.reset(); base54.reset();
toplevel.compute_char_frequency(options.mangle); toplevel.compute_char_frequency(options.mangle);
toplevel.mangle_names(options.mangle); toplevel.mangle_names(options.mangle);
if (options.mangle.properties) {
toplevel = mangle_properties(toplevel, options.mangle.properties);
}
} }
if (timings) timings.properties = Date.now();
if (options.mangle && options.mangle.properties) {
toplevel = mangle_properties(toplevel, options.mangle.properties);
}
if (timings) timings.output = Date.now();
var result = {}; var result = {};
if (options.output.ast) { if (options.output.ast) {
result.ast = toplevel; result.ast = toplevel;
@@ -123,7 +132,9 @@ function minify(files, options) {
root: options.sourceMap.root root: options.sourceMap.root
}); });
if (options.sourceMap.includeSources) { if (options.sourceMap.includeSources) {
for (var name in files) { if (files instanceof AST_Toplevel) {
throw new Error("original source content unavailable");
} else for (var name in files) if (HOP(files, name)) {
options.output.source_map.get().setSourceContent(name, files[name]); options.output.source_map.get().setSourceContent(name, files[name]);
} }
} }
@@ -142,6 +153,18 @@ function minify(files, options) {
} }
} }
} }
if (timings) {
timings.end = Date.now();
result.timings = {
parse: 1e-3 * (timings.scope1 - timings.parse),
scope: 1e-3 * (timings.compress - timings.scope1 + timings.mangle - timings.scope2),
compress: 1e-3 * (timings.scope2 - timings.compress),
mangle: 1e-3 * (timings.properties - timings.mangle),
properties: 1e-3 * (timings.output - timings.properties),
output: 1e-3 * (timings.end - timings.output),
total: 1e-3 * (timings.end - timings.start)
}
}
if (warnings.length) { if (warnings.length) {
result.warnings = warnings; result.warnings = warnings;
} }

View File

@@ -70,8 +70,7 @@ function OutputStream(options) {
semicolons : true, semicolons : true,
shebang : true, shebang : true,
source_map : null, source_map : null,
space_colon : true, webkit : false,
unescape_regexps : false,
width : 80, width : 80,
wrap_iife : false, wrap_iife : false,
}, true); }, true);
@@ -191,12 +190,43 @@ function OutputStream(options) {
var might_need_semicolon = false; var might_need_semicolon = false;
var might_add_newline = 0; var might_add_newline = 0;
var last = ""; var last = "";
var mapping_token, mapping_name, mappings = options.source_map && [];
var do_add_mapping = mappings ? function() {
mappings.forEach(function(mapping) {
try {
options.source_map.add(
mapping.token.file,
mapping.line, mapping.col,
mapping.token.line, mapping.token.col,
!mapping.name && mapping.token.type == "name" ? mapping.token.value : mapping.name
);
} catch(ex) {
AST_Node.warn("Couldn't figure out mapping for {file}:{line},{col} → {cline},{ccol} [{name}]", {
file: mapping.token.file,
line: mapping.token.line,
col: mapping.token.col,
cline: mapping.line,
ccol: mapping.col,
name: mapping.name || ""
})
}
});
mappings = [];
} : noop;
var ensure_line_len = options.max_line_len ? function() { var ensure_line_len = options.max_line_len ? function() {
if (current_col > options.max_line_len) { if (current_col > options.max_line_len) {
if (might_add_newline) { if (might_add_newline) {
var left = OUTPUT.slice(0, might_add_newline); var left = OUTPUT.slice(0, might_add_newline);
var right = OUTPUT.slice(might_add_newline); var right = OUTPUT.slice(might_add_newline);
if (mappings) {
var delta = right.length - current_col;
mappings.forEach(function(mapping) {
mapping.line++;
mapping.col += delta;
});
}
OUTPUT = left + "\n" + right; OUTPUT = left + "\n" + right;
current_line++; current_line++;
current_pos++; current_pos++;
@@ -206,7 +236,10 @@ function OutputStream(options) {
AST_Node.warn("Output exceeds {max_line_len} characters", options); AST_Node.warn("Output exceeds {max_line_len} characters", options);
} }
} }
might_add_newline = 0; if (might_add_newline) {
might_add_newline = 0;
do_add_mapping();
}
} : noop; } : noop;
var requireSemicolonChars = makePredicate("( [ + * / - , ."); var requireSemicolonChars = makePredicate("( [ + * / - , .");
@@ -266,6 +299,18 @@ function OutputStream(options) {
} }
might_need_space = false; might_need_space = false;
} }
if (mapping_token) {
mappings.push({
token: mapping_token,
name: mapping_name,
line: current_line,
col: current_col
});
mapping_token = false;
if (!might_add_newline) do_add_mapping();
}
OUTPUT += str; OUTPUT += str;
current_pos += str.length; current_pos += str.length;
var a = str.split(/\r?\n/), n = a.length - 1; var a = str.split(/\r?\n/), n = a.length - 1;
@@ -357,27 +402,12 @@ function OutputStream(options) {
function colon() { function colon() {
print(":"); print(":");
if (options.space_colon) space(); space();
}; };
var add_mapping = options.source_map ? function(token, name) { var add_mapping = mappings ? function(token, name) {
try { mapping_token = token;
if (token) options.source_map.add( mapping_name = name;
token.file || "?",
current_line, current_col,
token.line, token.col,
(!name && token.type == "name") ? token.value : name
);
} catch(ex) {
AST_Node.warn("Couldn't figure out mapping for {file}:{line},{col} → {cline},{ccol} [{name}]", {
file: token.file,
line: token.line,
col: token.col,
cline: current_line,
ccol: current_col,
name: name || ""
})
}
} : noop; } : noop;
function get() { function get() {
@@ -568,6 +598,13 @@ function OutputStream(options) {
return true; return true;
} }
if (output.option('webkit')) {
var p = output.parent();
if (p instanceof AST_PropAccess && p.expression === this) {
return true;
}
}
if (output.option('wrap_iife')) { if (output.option('wrap_iife')) {
var p = output.parent(); var p = output.parent();
return p instanceof AST_Call && p.expression === this; return p instanceof AST_Call && p.expression === this;
@@ -1205,9 +1242,8 @@ function OutputStream(options) {
}); });
else output.print("{}"); else output.print("{}");
}); });
DEFPRINT(AST_ObjectKeyVal, function(self, output){
var key = self.key; function print_property_name(key, quote, output) {
var quote = self.quote;
if (output.option("quote_keys")) { if (output.option("quote_keys")) {
output.print_string(key + ""); output.print_string(key + "");
} else if ((typeof key == "number" } else if ((typeof key == "number"
@@ -1224,20 +1260,24 @@ function OutputStream(options) {
} else { } else {
output.print_string(key, quote); output.print_string(key, quote);
} }
}
DEFPRINT(AST_ObjectKeyVal, function(self, output){
print_property_name(self.key, self.quote, output);
output.colon(); output.colon();
self.value.print(output); self.value.print(output);
}); });
DEFPRINT(AST_ObjectSetter, function(self, output){ AST_ObjectProperty.DEFMETHOD("_print_getter_setter", function(type, output) {
output.print("set"); output.print(type);
output.space(); output.space();
self.key.print(output); print_property_name(this.key.name, this.quote, output);
self.value._do_print(output, true); this.value._do_print(output, true);
});
DEFPRINT(AST_ObjectSetter, function(self, output){
self._print_getter_setter("set", output);
}); });
DEFPRINT(AST_ObjectGetter, function(self, output){ DEFPRINT(AST_ObjectGetter, function(self, output){
output.print("get"); self._print_getter_setter("get", output);
output.space();
self.key.print(output);
self.value._do_print(output, true);
}); });
DEFPRINT(AST_Symbol, function(self, output){ DEFPRINT(AST_Symbol, function(self, output){
var def = self.definition(); var def = self.definition();
@@ -1261,45 +1301,14 @@ function OutputStream(options) {
} }
}); });
function regexp_safe_literal(code) {
return [
0x5c , // \
0x2f , // /
0x2e , // .
0x2b , // +
0x2a , // *
0x3f , // ?
0x28 , // (
0x29 , // )
0x5b , // [
0x5d , // ]
0x7b , // {
0x7d , // }
0x24 , // $
0x5e , // ^
0x3a , // :
0x7c , // |
0x21 , // !
0x0a , // \n
0x0d , // \r
0x00 , // \0
0xfeff , // Unicode BOM
0x2028 , // unicode "line separator"
0x2029 , // unicode "paragraph separator"
].indexOf(code) < 0;
};
DEFPRINT(AST_RegExp, function(self, output){ DEFPRINT(AST_RegExp, function(self, output){
var str = self.getValue().toString(); var regexp = self.getValue();
var str = regexp.toString();
if (regexp.raw_source) {
str = "/" + regexp.raw_source + str.slice(str.lastIndexOf("/"));
}
if (output.option("ascii_only")) { if (output.option("ascii_only")) {
str = output.to_ascii(str); str = output.to_ascii(str);
} else if (output.option("unescape_regexps")) {
str = str.split("\\\\").map(function(str){
return str.replace(/\\u[0-9a-fA-F]{4}|\\x[0-9a-fA-F]{2}/g, function(s){
var code = parseInt(s.substr(2), 16);
return regexp_safe_literal(code) ? String.fromCharCode(code) : s;
});
}).join("\\\\");
} }
output.print(str); output.print(str);
var p = output.parent(); var p = output.parent();

View File

@@ -115,8 +115,6 @@ var PUNC_BEFORE_EXPRESSION = makePredicate(characters("[{(,;:"));
var PUNC_CHARS = makePredicate(characters("[]{}(),;:")); var PUNC_CHARS = makePredicate(characters("[]{}(),;:"));
var REGEXP_MODIFIERS = makePredicate(characters("gmsiy"));
/* -----[ Tokenizer ]----- */ /* -----[ Tokenizer ]----- */
// regexps adapted from http://xregexp.com/plugins/#unicode // regexps adapted from http://xregexp.com/plugins/#unicode
@@ -285,7 +283,11 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
S.regex_allowed = ((type == "operator" && !UNARY_POSTFIX(value)) || S.regex_allowed = ((type == "operator" && !UNARY_POSTFIX(value)) ||
(type == "keyword" && KEYWORDS_BEFORE_EXPRESSION(value)) || (type == "keyword" && KEYWORDS_BEFORE_EXPRESSION(value)) ||
(type == "punc" && PUNC_BEFORE_EXPRESSION(value))); (type == "punc" && PUNC_BEFORE_EXPRESSION(value)));
prev_was_dot = (type == "punc" && value == "."); if (type == "punc" && value == ".") {
prev_was_dot = true;
} else if (!is_comment) {
prev_was_dot = false;
}
var ret = { var ret = {
type : type, type : type,
value : value, value : value,
@@ -473,31 +475,33 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
return name; return name;
}; };
var read_regexp = with_eof_error("Unterminated regular expression", function(regexp){ var read_regexp = with_eof_error("Unterminated regular expression", function(source) {
var prev_backslash = false, ch, in_class = false; var prev_backslash = false, ch, in_class = false;
while ((ch = next(true))) if (NEWLINE_CHARS(ch)) { while ((ch = next(true))) if (NEWLINE_CHARS(ch)) {
parse_error("Unexpected line terminator"); parse_error("Unexpected line terminator");
} else if (prev_backslash) { } else if (prev_backslash) {
regexp += "\\" + ch; source += "\\" + ch;
prev_backslash = false; prev_backslash = false;
} else if (ch == "[") { } else if (ch == "[") {
in_class = true; in_class = true;
regexp += ch; source += ch;
} else if (ch == "]" && in_class) { } else if (ch == "]" && in_class) {
in_class = false; in_class = false;
regexp += ch; source += ch;
} else if (ch == "/" && !in_class) { } else if (ch == "/" && !in_class) {
break; break;
} else if (ch == "\\") { } else if (ch == "\\") {
prev_backslash = true; prev_backslash = true;
} else { } else {
regexp += ch; source += ch;
} }
var mods = read_name(); var mods = read_name();
try { try {
return token("regexp", new RegExp(regexp, mods)); var regexp = new RegExp(source, mods);
regexp.raw_source = source;
return token("regexp", regexp);
} catch(e) { } catch(e) {
parse_error(e.message); parse_error(e.message);
} }
}); });
@@ -678,9 +682,7 @@ var PRECEDENCE = (function(a, ret){
{} {}
); );
var STATEMENTS_WITH_LABELS = array_to_hash([ "for", "do", "while", "switch" ]); var ATOMIC_START_TOKEN = makePredicate([ "atom", "num", "string", "regexp", "name" ]);
var ATOMIC_START_TOKEN = array_to_hash([ "atom", "num", "string", "regexp", "name" ]);
/* -----[ Parser ]----- */ /* -----[ Parser ]----- */
@@ -694,7 +696,7 @@ function parse($TEXT, options) {
shebang : true, shebang : true,
strict : false, strict : false,
toplevel : null, toplevel : null,
}); }, true);
var S = { var S = {
input : (typeof $TEXT == "string" input : (typeof $TEXT == "string"
@@ -801,17 +803,16 @@ function parse($TEXT, options) {
}; };
var statement = embed_tokens(function() { var statement = embed_tokens(function() {
var tmp;
handle_regexp(); handle_regexp();
switch (S.token.type) { switch (S.token.type) {
case "string": case "string":
if (S.in_directives) { if (S.in_directives) {
tmp = peek(); var token = peek();
if (S.token.raw.indexOf("\\") == -1 if (S.token.raw.indexOf("\\") == -1
&& (tmp.nlb && (token.nlb
|| is_token(tmp, "eof") || is_token(token, "eof")
|| is_token(tmp, "punc", ";") || is_token(token, "punc", ";")
|| is_token(tmp, "punc", "}"))) { || is_token(token, "punc", "}"))) {
S.input.add_directive(S.token.value); S.input.add_directive(S.token.value);
} else { } else {
S.in_directives = false; S.in_directives = false;
@@ -850,72 +851,97 @@ function parse($TEXT, options) {
} }
case "keyword": case "keyword":
switch (tmp = S.token.value, next(), tmp) { switch (S.token.value) {
case "break": case "break":
next();
return break_cont(AST_Break); return break_cont(AST_Break);
case "continue": case "continue":
next();
return break_cont(AST_Continue); return break_cont(AST_Continue);
case "debugger": case "debugger":
next();
semicolon(); semicolon();
return new AST_Debugger(); return new AST_Debugger();
case "do": case "do":
next();
var body = in_loop(statement);
expect_token("keyword", "while");
var condition = parenthesised();
semicolon(true);
return new AST_Do({ return new AST_Do({
body : in_loop(statement), body : body,
condition : (expect_token("keyword", "while"), tmp = parenthesised(), semicolon(true), tmp) condition : condition
}); });
case "while": case "while":
next();
return new AST_While({ return new AST_While({
condition : parenthesised(), condition : parenthesised(),
body : in_loop(statement) body : in_loop(statement)
}); });
case "for": case "for":
next();
return for_(); return for_();
case "function": case "function":
next();
return function_(AST_Defun); return function_(AST_Defun);
case "if": case "if":
next();
return if_(); return if_();
case "return": case "return":
if (S.in_function == 0 && !options.bare_returns) if (S.in_function == 0 && !options.bare_returns)
croak("'return' outside of function"); croak("'return' outside of function");
next();
var value = null;
if (is("punc", ";")) {
next();
} else if (!can_insert_semicolon()) {
value = expression(true);
semicolon();
}
return new AST_Return({ return new AST_Return({
value: ( is("punc", ";") value: value
? (next(), null)
: can_insert_semicolon()
? null
: (tmp = expression(true), semicolon(), tmp) )
}); });
case "switch": case "switch":
next();
return new AST_Switch({ return new AST_Switch({
expression : parenthesised(), expression : parenthesised(),
body : in_loop(switch_body_) body : in_loop(switch_body_)
}); });
case "throw": case "throw":
next();
if (S.token.nlb) if (S.token.nlb)
croak("Illegal newline after 'throw'"); croak("Illegal newline after 'throw'");
var value = expression(true);
semicolon();
return new AST_Throw({ return new AST_Throw({
value: (tmp = expression(true), semicolon(), tmp) value: value
}); });
case "try": case "try":
next();
return try_(); return try_();
case "var": case "var":
return tmp = var_(), semicolon(), tmp; next();
var node = var_();
semicolon();
return node;
case "with": case "with":
if (S.input.has_directive("use strict")) { if (S.input.has_directive("use strict")) {
croak("Strict mode may not include a with statement"); croak("Strict mode may not include a with statement");
} }
next();
return new AST_With({ return new AST_With({
expression : parenthesised(), expression : parenthesised(),
body : statement() body : statement()
@@ -1192,7 +1218,6 @@ function parse($TEXT, options) {
var tok = S.token, ret; var tok = S.token, ret;
switch (tok.type) { switch (tok.type) {
case "name": case "name":
case "keyword":
ret = _make_symbol(AST_SymbolRef); ret = _make_symbol(AST_SymbolRef);
break; break;
case "num": case "num":
@@ -1222,13 +1247,6 @@ function parse($TEXT, options) {
break; break;
} }
break; break;
case "operator":
if (!is_identifier_string(tok.value)) {
croak("Invalid getter/setter name: " + tok.value,
tok.line, tok.col, tok.pos);
}
ret = _make_symbol(AST_SymbolRef);
break;
} }
next(); next();
return ret; return ret;
@@ -1262,7 +1280,7 @@ function parse($TEXT, options) {
func.end = prev(); func.end = prev();
return subscripts(func, allow_calls); return subscripts(func, allow_calls);
} }
if (ATOMIC_START_TOKEN[S.token.type]) { if (ATOMIC_START_TOKEN(S.token.type)) {
return subscripts(as_atom_node(), allow_calls); return subscripts(as_atom_node(), allow_calls);
} }
unexpected(); unexpected();

View File

@@ -78,7 +78,8 @@ function mangle_properties(ast, options) {
reserved: null, reserved: null,
}); });
var reserved = options.reserved || []; var reserved = options.reserved;
if (!Array.isArray(reserved)) reserved = [];
if (!options.builtins) find_builtins(reserved); if (!options.builtins) find_builtins(reserved);
var cache = options.cache; var cache = options.cache;

View File

@@ -183,16 +183,8 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
self.walk(tw); self.walk(tw);
// pass 2: find back references and eval // pass 2: find back references and eval
var func = null; self.globals = new Dictionary();
var globals = self.globals = new Dictionary();
var tw = new TreeWalker(function(node, descend){ var tw = new TreeWalker(function(node, descend){
if (node instanceof AST_Lambda) {
var prev_func = func;
func = node;
descend();
func = prev_func;
return true;
}
if (node instanceof AST_LoopControl && node.label) { if (node instanceof AST_LoopControl && node.label) {
node.label.thedef.references.push(node); node.label.thedef.references.push(node);
return true; return true;
@@ -361,9 +353,7 @@ AST_Symbol.DEFMETHOD("unmangleable", function(options){
}); });
// labels are always mangleable // labels are always mangleable
AST_Label.DEFMETHOD("unmangleable", function(){ AST_Label.DEFMETHOD("unmangleable", return_false);
return false;
});
AST_Symbol.DEFMETHOD("unreferenced", function(){ AST_Symbol.DEFMETHOD("unreferenced", function(){
return this.definition().references.length == 0 return this.definition().references.length == 0
@@ -374,13 +364,9 @@ AST_Symbol.DEFMETHOD("undeclared", function(){
return this.definition().undeclared; return this.definition().undeclared;
}); });
AST_LabelRef.DEFMETHOD("undeclared", function(){ AST_LabelRef.DEFMETHOD("undeclared", return_false);
return false;
});
AST_Label.DEFMETHOD("undeclared", function(){ AST_Label.DEFMETHOD("undeclared", return_false);
return false;
});
AST_Symbol.DEFMETHOD("definition", function(){ AST_Symbol.DEFMETHOD("definition", function(){
return this.thedef; return this.thedef;
@@ -391,13 +377,15 @@ AST_Symbol.DEFMETHOD("global", function(){
}); });
AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){ AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){
return defaults(options, { options = defaults(options, {
eval : false, eval : false,
ie8 : false, ie8 : false,
keep_fnames : false, keep_fnames : false,
reserved : [], reserved : [],
toplevel : false, toplevel : false,
}); });
if (!Array.isArray(options.reserved)) options.reserved = [];
return options;
}); });
AST_Toplevel.DEFMETHOD("mangle_names", function(options){ AST_Toplevel.DEFMETHOD("mangle_names", function(options){

View File

@@ -43,13 +43,6 @@
"use strict"; "use strict";
function array_to_hash(a) {
var ret = Object.create(null);
for (var i = 0; i < a.length; ++i)
ret[a[i]] = true;
return ret;
};
function slice(a, start) { function slice(a, start) {
return Array.prototype.slice.call(a, start || 0); return Array.prototype.slice.call(a, start || 0);
}; };

View File

@@ -4,7 +4,7 @@
"homepage": "http://lisperator.net/uglifyjs", "homepage": "http://lisperator.net/uglifyjs",
"author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)", "author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"version": "3.0.5", "version": "3.0.16",
"engines": { "engines": {
"node": ">=0.8.0" "node": ">=0.8.0"
}, },
@@ -34,10 +34,11 @@
}, },
"devDependencies": { "devDependencies": {
"acorn": "~5.0.3", "acorn": "~5.0.3",
"mocha": "~2.3.4" "mocha": "~2.3.4",
"semver": "~5.3.0"
}, },
"scripts": { "scripts": {
"test": "node test/run-tests.js" "test": "node test/run-tests.js"
}, },
"keywords": ["uglify", "uglify-js", "minify", "minifier"] "keywords": ["uglify", "uglify-js", "minify", "minifier", "es5"]
} }

View File

@@ -4,12 +4,13 @@
"use strict"; "use strict";
var createHash = require("crypto").createHash; var createHash = require("crypto").createHash;
var fetch = require("./fetch");
var fork = require("child_process").fork; var fork = require("child_process").fork;
var args = process.argv.slice(2); var args = process.argv.slice(2);
if (!args.length) { if (!args.length) {
args.push("-mc"); args.push("-mc");
} }
args.push("--stats"); args.push("--timings");
var urls = [ var urls = [
"https://code.jquery.com/jquery-3.2.1.js", "https://code.jquery.com/jquery-3.2.1.js",
"https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.4/angular.js", "https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.4/angular.js",
@@ -29,12 +30,7 @@ function done() {
var info = results[url]; var info = results[url];
console.log(); console.log();
console.log(url); console.log(url);
var elapsed = 0; console.log(info.log);
console.log(info.log.replace(/Elapsed: ([0-9]+)\s*/g, function(match, time) {
elapsed += 1e-3 * parseInt(time);
return "";
}));
console.log("Run-time:", elapsed.toFixed(3), "s");
console.log("Original:", info.input, "bytes"); console.log("Original:", info.input, "bytes");
console.log("Uglified:", info.output, "bytes"); console.log("Uglified:", info.output, "bytes");
console.log("SHA1 sum:", info.sha1); console.log("SHA1 sum:", info.sha1);
@@ -57,7 +53,8 @@ urls.forEach(function(url) {
output: 0, output: 0,
log: "" log: ""
}; };
require(url.slice(0, url.indexOf(":"))).get(url, function(res) { fetch(url, function(err, res) {
if (err) throw err;
var uglifyjs = fork("bin/uglifyjs", args, { silent: true }); var uglifyjs = fork("bin/uglifyjs", args, { silent: true });
res.on("data", function(data) { res.on("data", function(data) {
results[url].input += data.length; results[url].input += data.length;

View File

@@ -1146,7 +1146,7 @@ collapse_vars_constants: {
function f3(x) { function f3(x) {
var b = x.prop; var b = x.prop;
sideeffect1(); sideeffect1();
return b + -9; return b + (function() { return -9; })();
} }
} }
} }

View File

@@ -31,7 +31,7 @@ dead_code_2_should_warn: {
function f() { function f() {
g(); g();
x = 10; x = 10;
throw "foo"; throw new Error("foo");
// completely discarding the `if` would introduce some // completely discarding the `if` would introduce some
// bugs. UglifyJS v1 doesn't deal with this issue; in v2 // bugs. UglifyJS v1 doesn't deal with this issue; in v2
// we copy any declarations to the upper scope. // we copy any declarations to the upper scope.
@@ -46,16 +46,60 @@ dead_code_2_should_warn: {
})(); })();
} }
} }
f();
} }
expect: { expect: {
function f() { function f() {
g(); g();
x = 10; x = 10;
throw "foo"; throw new Error("foo");
var x; var x;
function g(){}; function g(){};
} }
f();
} }
expect_stdout: true
node_version: "<=4"
}
dead_code_2_should_warn_strict: {
options = {
dead_code: true
};
input: {
"use strict";
function f() {
g();
x = 10;
throw new Error("foo");
// completely discarding the `if` would introduce some
// bugs. UglifyJS v1 doesn't deal with this issue; in v2
// we copy any declarations to the upper scope.
if (x) {
y();
var x;
function g(){};
// but nested declarations should not be kept.
(function(){
var q;
function y(){};
})();
}
}
f();
}
expect: {
"use strict";
function f() {
g();
x = 10;
throw new Error("foo");
var x;
}
f();
}
expect_stdout: true
node_version: ">=4"
} }
dead_code_constant_boolean_should_warn_more: { dead_code_constant_boolean_should_warn_more: {
@@ -78,6 +122,7 @@ dead_code_constant_boolean_should_warn_more: {
foo(); foo();
var moo; var moo;
} }
bar();
} }
expect: { expect: {
var foo; var foo;
@@ -86,8 +131,46 @@ dead_code_constant_boolean_should_warn_more: {
// as for the for, it should keep: // as for the for, it should keep:
var x = 10, y; var x = 10, y;
var moo; var moo;
bar();
} }
expect_stdout: true expect_stdout: true
node_version: "<=4"
}
dead_code_constant_boolean_should_warn_more_strict: {
options = {
dead_code : true,
loops : true,
booleans : true,
conditionals : true,
evaluate : true,
side_effects : true,
};
input: {
"use strict";
while (!((foo && bar) || (x + "0"))) {
console.log("unreachable");
var foo;
function bar() {}
}
for (var x = 10, y; x && (y || x) && (!typeof x); ++x) {
asdf();
foo();
var moo;
}
bar();
}
expect: {
"use strict";
var foo;
// nothing for the while
// as for the for, it should keep:
var x = 10, y;
var moo;
bar();
}
expect_stdout: true
node_version: ">=4"
} }
try_catch_finally: { try_catch_finally: {

View File

@@ -751,12 +751,12 @@ issue_1583: {
expect: { expect: {
function m(t) { function m(t) {
(function(e) { (function(e) {
t = (function() { t = e();
return (function(a) { })(function() {
return a; return (function(a) {
})(function(a) {}); return a;
})(); })(function(a) {});
})(); });
} }
} }
} }

View File

@@ -644,6 +644,7 @@ unsafe_prototype_function: {
call_args: { call_args: {
options = { options = {
evaluate: true, evaluate: true,
inline: true,
reduce_vars: true, reduce_vars: true,
toplevel: true, toplevel: true,
} }
@@ -665,6 +666,7 @@ call_args: {
call_args_drop_param: { call_args_drop_param: {
options = { options = {
evaluate: true, evaluate: true,
inline: true,
keep_fargs: false, keep_fargs: false,
reduce_vars: true, reduce_vars: true,
toplevel: true, toplevel: true,
@@ -780,13 +782,15 @@ unsafe_charAt_noop: {
input: { input: {
console.log( console.log(
s.charAt(0), s.charAt(0),
"string".charAt(x) "string".charAt(x),
(typeof x).charAt()
); );
} }
expect: { expect: {
console.log( console.log(
s.charAt(0), s.charAt(0),
"string".charAt(x) "string".charAt(x),
(typeof x)[0]
); );
} }
} }
@@ -990,3 +994,78 @@ Infinity_NaN_undefined_LHS: {
"}", "}",
] ]
} }
issue_1964_1: {
options = {
evaluate: true,
reduce_vars: true,
unsafe_regexp: false,
unused: true,
}
input: {
function f() {
var long_variable_name = /\s/;
return "a b c".split(long_variable_name)[1];
}
console.log(f());
}
expect: {
function f() {
var long_variable_name = /\s/;
return "a b c".split(long_variable_name)[1];
}
console.log(f());
}
expect_stdout: "b"
}
issue_1964_2: {
options = {
evaluate: true,
reduce_vars: true,
unsafe_regexp: true,
unused: true,
}
input: {
function f() {
var long_variable_name = /\s/;
return "a b c".split(long_variable_name)[1];
}
console.log(f());
}
expect: {
function f() {
return "a b c".split(/\s/)[1];
}
console.log(f());
}
expect_stdout: "b"
}
array_slice_index: {
options = {
evaluate: true,
unsafe: true,
}
input: {
console.log([1,2,3].slice(1)[1]);
}
expect: {
console.log(3);
}
expect_stdout: "3"
}
string_charCodeAt: {
options = {
evaluate: true,
unsafe: true,
}
input: {
console.log("foo".charCodeAt("bar".length));
}
expect: {
console.log(NaN);
}
expect_stdout: "NaN"
}

View File

@@ -21,6 +21,7 @@ iifes_returning_constants_keep_fargs_true: {
join_vars : true, join_vars : true,
reduce_vars : true, reduce_vars : true,
cascade : true, cascade : true,
inline : true,
} }
input: { input: {
(function(){ return -1.23; }()); (function(){ return -1.23; }());
@@ -56,6 +57,7 @@ iifes_returning_constants_keep_fargs_false: {
join_vars : true, join_vars : true,
reduce_vars : true, reduce_vars : true,
cascade : true, cascade : true,
inline : true,
} }
input: { input: {
(function(){ return -1.23; }()); (function(){ return -1.23; }());
@@ -82,6 +84,7 @@ issue_485_crashing_1530: {
conditionals: true, conditionals: true,
dead_code: true, dead_code: true,
evaluate: true, evaluate: true,
inline: true,
} }
input: { input: {
(function(a) { (function(a) {
@@ -154,6 +157,7 @@ function_returning_constant_literal: {
evaluate: true, evaluate: true,
cascade: true, cascade: true,
unused: true, unused: true,
inline: true,
} }
input: { input: {
function greeter() { function greeter() {
@@ -167,3 +171,168 @@ function_returning_constant_literal: {
} }
expect_stdout: "Hello there" expect_stdout: "Hello there"
} }
hoist_funs: {
options = {
hoist_funs: true,
}
input: {
console.log(1, typeof f, typeof g);
if (console.log(2, typeof f, typeof g))
console.log(3, typeof f, typeof g);
else {
console.log(4, typeof f, typeof g);
function f() {}
console.log(5, typeof f, typeof g);
}
function g() {}
console.log(6, typeof f, typeof g);
}
expect: {
function f() {}
function g() {}
console.log(1, typeof f, typeof g);
if (console.log(2, typeof f, typeof g))
console.log(3, typeof f, typeof g);
else {
console.log(4, typeof f, typeof g);
console.log(5, typeof f, typeof g);
}
console.log(6, typeof f, typeof g);
}
expect_stdout: [
"1 'function' 'function'",
"2 'function' 'function'",
"4 'function' 'function'",
"5 'function' 'function'",
"6 'function' 'function'",
]
node_version: "<=4"
}
hoist_funs_strict: {
options = {
hoist_funs: true,
}
input: {
"use strict";
console.log(1, typeof f, typeof g);
if (console.log(2, typeof f, typeof g))
console.log(3, typeof f, typeof g);
else {
console.log(4, typeof f, typeof g);
function f() {}
console.log(5, typeof f, typeof g);
}
function g() {}
console.log(6, typeof f, typeof g);
}
expect: {
"use strict";
function g() {}
console.log(1, typeof f, typeof g);
if (console.log(2, typeof f, typeof g))
console.log(3, typeof f, typeof g);
else {
console.log(4, typeof f, typeof g);
function f() {}
console.log(5, typeof f, typeof g);
}
console.log(6, typeof f, typeof g);
}
expect_stdout: [
"1 'undefined' 'function'",
"2 'undefined' 'function'",
"4 'function' 'function'",
"5 'function' 'function'",
"6 'undefined' 'function'",
]
node_version: ">=4"
}
issue_203: {
options = {
keep_fargs: false,
side_effects: true,
unsafe_Func: true,
unused: true,
}
input: {
var m = {};
var fn = Function("require", "module", "exports", "module.exports = 42;");
fn(null, m, m.exports);
console.log(m.exports);
}
expect: {
var m = {};
var fn = Function("a", "b", "b.exports=42");
fn(null, m, m.exports);
console.log(m.exports);
}
expect_stdout: "42"
}
no_webkit: {
beautify = {
webkit: false,
}
input: {
console.log(function() {
1 + 1;
}.a = 1);
}
expect_exact: "console.log(function(){1+1}.a=1);"
expect_stdout: "1"
}
webkit: {
beautify = {
webkit: true,
}
input: {
console.log(function() {
1 + 1;
}.a = 1);
}
expect_exact: "console.log((function(){1+1}).a=1);"
expect_stdout: "1"
}
issue_2084: {
options = {
collapse_vars: true,
conditionals: true,
evaluate: true,
inline: true,
passes: 2,
reduce_vars: true,
sequences: true,
side_effects: true,
unused: true,
}
input: {
var c = 0;
!function() {
!function(c) {
c = 1 + c;
var c = 0;
function f14(a_1) {
if (c = 1 + c, 0 !== 23..toString())
c = 1 + c, a_1 && (a_1[0] = 0);
}
f14();
}(-1);
}();
console.log(c);
}
expect: {
var c = 0;
!function(c) {
c = 1 + c,
c = 1 + (c = 0),
0 !== 23..toString() && (c = 1 + c);
}(-1),
console.log(c);
}
expect_stdout: "0"
}

View File

@@ -160,3 +160,17 @@ issue_1801: {
console.log(!0); console.log(!0);
} }
} }
issue_1986: {
options = {
global_defs: {
"@alert": "console.log",
},
}
input: {
alert(42);
}
expect: {
console.log(42);
}
}

View File

@@ -302,3 +302,27 @@ issue_1437_conditionals: {
} }
} }
} }
issue_512: {
options = {
conditionals: true,
if_return: true,
sequences: true,
side_effects: true,
}
input: {
function a() {
if (b()) {
c();
return;
}
throw e;
}
}
expect: {
function a() {
if (!b()) throw e;
c();
}
}
}

View File

@@ -116,3 +116,137 @@ non_hoisted_function_after_return_2b: {
"WARN: Dropping unreachable code [test/compress/issue-1034.js:101,12]", "WARN: Dropping unreachable code [test/compress/issue-1034.js:101,12]",
] ]
} }
non_hoisted_function_after_return_strict: {
options = {
hoist_funs: false, dead_code: true, conditionals: true, comparisons: true,
evaluate: true, booleans: true, loops: true, unused: true, keep_fargs: true,
if_return: true, join_vars: true, cascade: true, side_effects: true
}
input: {
"use strict";
function foo(x) {
if (x) {
return bar();
not_called1();
} else {
return baz();
not_called2();
}
function bar() { return 7; }
return not_reached;
function UnusedFunction() {}
function baz() { return 8; }
}
console.log(foo(0), foo(1));
}
expect: {
"use strict";
function foo(x) {
return x ? bar() : baz();
function bar() { return 7 }
function baz() { return 8 }
}
console.log(foo(0), foo(1));
}
expect_stdout: "8 7"
expect_warnings: [
'WARN: Dropping unreachable code [test/compress/issue-1034.js:131,16]',
"WARN: Dropping unreachable code [test/compress/issue-1034.js:134,16]",
"WARN: Dropping unreachable code [test/compress/issue-1034.js:137,12]",
"WARN: Dropping unused function UnusedFunction [test/compress/issue-1034.js:138,21]"
]
}
non_hoisted_function_after_return_2a_strict: {
options = {
hoist_funs: false, dead_code: true, conditionals: true, comparisons: true,
evaluate: true, booleans: true, loops: true, unused: true, keep_fargs: true,
if_return: true, join_vars: true, cascade: true, side_effects: true,
collapse_vars: false, passes: 2, warnings: "verbose"
}
input: {
"use strict";
function foo(x) {
if (x) {
return bar(1);
var a = not_called(1);
} else {
return bar(2);
var b = not_called(2);
}
var c = bar(3);
function bar(x) { return 7 - x; }
function nope() {}
return b || c;
}
console.log(foo(0), foo(1));
}
expect: {
"use strict";
function foo(x) {
return bar(x ? 1 : 2);
function bar(x) {
return 7 - x;
}
}
console.log(foo(0), foo(1));
}
expect_stdout: "5 6"
expect_warnings: [
"WARN: Dropping unreachable code [test/compress/issue-1034.js:173,16]",
"WARN: Declarations in unreachable code! [test/compress/issue-1034.js:173,16]",
"WARN: Dropping unreachable code [test/compress/issue-1034.js:176,16]",
"WARN: Declarations in unreachable code! [test/compress/issue-1034.js:176,16]",
"WARN: Dropping unused variable a [test/compress/issue-1034.js:173,20]",
"WARN: Dropping unused function nope [test/compress/issue-1034.js:180,21]",
"WARN: Dropping unreachable code [test/compress/issue-1034.js:178,12]",
"WARN: Declarations in unreachable code! [test/compress/issue-1034.js:178,12]",
"WARN: Dropping unreachable code [test/compress/issue-1034.js:181,12]",
"WARN: Dropping unused variable b [test/compress/issue-1034.js:176,20]",
"WARN: Dropping unused variable c [test/compress/issue-1034.js:178,16]",
]
}
non_hoisted_function_after_return_2b_strict: {
options = {
hoist_funs: false, dead_code: true, conditionals: true, comparisons: true,
evaluate: true, booleans: true, loops: true, unused: true, keep_fargs: true,
if_return: true, join_vars: true, cascade: true, side_effects: true,
collapse_vars: false
}
input: {
"use strict";
function foo(x) {
if (x) {
return bar(1);
} else {
return bar(2);
var b;
}
var c = bar(3);
function bar(x) {
return 7 - x;
}
return b || c;
}
console.log(foo(0), foo(1));
}
expect: {
"use strict";
function foo(x) {
return bar(x ? 1 : 2);
function bar(x) { return 7 - x; }
}
console.log(foo(0), foo(1));
}
expect_stdout: "5 6"
expect_warnings: [
// duplicate warnings no longer emitted
"WARN: Dropping unreachable code [test/compress/issue-1034.js:225,16]",
"WARN: Declarations in unreachable code! [test/compress/issue-1034.js:225,16]",
"WARN: Dropping unreachable code [test/compress/issue-1034.js:227,12]",
"WARN: Declarations in unreachable code! [test/compress/issue-1034.js:227,12]",
"WARN: Dropping unreachable code [test/compress/issue-1034.js:231,12]",
]
}

View File

@@ -1,90 +1,91 @@
multiple_functions: { multiple_functions: {
options = { if_return: true, hoist_funs: false }; options = {
hoist_funs: false,
if_return: true,
}
input: { input: {
( function() { ( function() {
if ( !window ) { if ( !window ) {
return; return;
} }
function f() {} function f() {}
function g() {} function g() {}
} )(); } )();
} }
expect: { expect: {
( function() { ( function() {
function f() {}
function g() {}
// NOTE: other compression steps will reduce this // NOTE: other compression steps will reduce this
// down to just `window`. // down to just `window`.
if ( window ); if ( window );
function f() {}
function g() {}
} )(); } )();
} }
} }
single_function: { single_function: {
options = { if_return: true, hoist_funs: false }; options = {
hoist_funs: false,
if_return: true,
}
input: { input: {
( function() { ( function() {
if ( !window ) { if ( !window ) {
return; return;
} }
function f() {} function f() {}
} )(); } )();
} }
expect: { expect: {
( function() { ( function() {
function f() {}
if ( window ); if ( window );
function f() {}
} )(); } )();
} }
} }
deeply_nested: { deeply_nested: {
options = { if_return: true, hoist_funs: false }; options = {
hoist_funs: false,
if_return: true,
}
input: { input: {
( function() { ( function() {
if ( !window ) { if ( !window ) {
return; return;
} }
function f() {} function f() {}
function g() {} function g() {}
if ( !document ) { if ( !document ) {
return; return;
} }
function h() {} function h() {}
} )(); } )();
} }
expect: { expect: {
( function() { ( function() {
function f() {}
function g() {}
function h() {}
// NOTE: other compression steps will reduce this // NOTE: other compression steps will reduce this
// down to just `window`. // down to just `window`.
if ( window ) if ( window )
if (document); if (document);
function f() {}
function g() {}
function h() {}
} )(); } )();
} }
} }
not_hoisted_when_already_nested: { not_hoisted_when_already_nested: {
options = { if_return: true, hoist_funs: false }; options = {
hoist_funs: false,
if_return: true,
}
input: { input: {
( function() { ( function() {
if ( !window ) { if ( !window ) {
return; return;
} }
if ( foo ) function f() {} if ( foo ) function f() {}
} )(); } )();
} }
expect: { expect: {
@@ -94,3 +95,70 @@ not_hoisted_when_already_nested: {
} )(); } )();
} }
} }
defun_if_return: {
options = {
hoist_funs: false,
if_return: true,
}
input: {
function e() {
function f() {}
if (!window) return;
else function g() {}
function h() {}
}
}
expect: {
function e() {
function f() {}
if (window) function g() {}
function h() {}
}
}
}
defun_hoist_funs: {
options = {
hoist_funs: true,
if_return: true,
}
input: {
function e() {
function f() {}
if (!window) return;
else function g() {}
function h() {}
}
}
expect: {
function e() {
function f() {}
function g() {}
function h() {}
if (window);
}
}
}
defun_else_if_return: {
options = {
hoist_funs: false,
if_return: true,
}
input: {
function e() {
function f() {}
if (window) function g() {}
else return;
function h() {}
}
}
expect: {
function e() {
function f() {}
if (window) function g() {}
function h() {}
}
}
}

View File

@@ -1,6 +1,7 @@
unary_prefix: { unary_prefix: {
options = { options = {
evaluate: true, evaluate: true,
inline: true,
reduce_vars: true, reduce_vars: true,
unused: true, unused: true,
} }

View File

@@ -0,0 +1,31 @@
operator: {
input: {
a. //comment
typeof
}
expect_exact: "a.typeof;"
}
name: {
input: {
a. //comment
b
}
expect_exact: "a.b;"
}
keyword: {
input: {
a. //comment
default
}
expect_exact: "a.default;"
}
atom: {
input: {
a. //comment
true
}
expect_exact: "a.true;"
}

483
test/compress/issue-281.js Normal file
View File

@@ -0,0 +1,483 @@
collapse_vars_constants: {
options = {
collapse_vars: true,
evaluate: true,
inline: true,
reduce_vars: true,
unused: true,
}
input: {
function f1(x) {
var a = 4, b = x.prop, c = 5, d = sideeffect1(), e = sideeffect2();
return b + (function() { return d - a * e - c; })();
}
function f2(x) {
var a = 4, b = x.prop, c = 5, not_used = sideeffect1(), e = sideeffect2();
return b + (function() { return -a * e - c; })();
}
}
expect: {
function f1(x) {
var b = x.prop, d = sideeffect1(), e = sideeffect2();
return b + (d - 4 * e - 5);
}
function f2(x) {
var b = x.prop;
sideeffect1();
return b + (-4 * sideeffect2() - 5);
}
}
}
modified: {
options = {
collapse_vars: true,
inline: true,
unused: true,
}
input: {
function f5(b) {
var a = function() {
return b;
}();
return b++ + a;
}
console.log(f5(1));
}
expect: {
function f5(b) {
var a = b;
return b++ + a;
}
console.log(f5(1));
}
expect_stdout: "2"
}
ref_scope: {
options = {
collapse_vars: true,
inline: true,
unused: true,
}
input: {
console.log(function() {
var a = 1, b = 2, c = 3;
var a = c++, b = b /= a;
return function() {
return a;
}() + b;
}());
}
expect: {
console.log(function() {
var a = 1, b = 2, c = 3;
b = b /= a = c++;
return a + b;
}());
}
expect_stdout: true
}
safe_undefined: {
options = {
conditionals: true,
if_return: true,
inline: true,
unsafe: false,
unused: true,
}
mangle = {}
input: {
var a, c;
console.log(function(undefined) {
return function() {
if (a)
return b;
if (c)
return d;
};
}(1)());
}
expect: {
var a, c;
console.log(a ? b : c ? d : void 0);
}
expect_stdout: true
}
negate_iife_3: {
options = {
conditionals: true,
expression: true,
inline: true,
negate_iife: true,
}
input: {
(function(){ return t })() ? console.log(true) : console.log(false);
}
expect: {
t ? console.log(true) : console.log(false);
}
}
negate_iife_3_off: {
options = {
conditionals: true,
expression: true,
inline: true,
negate_iife: false,
}
input: {
(function(){ return t })() ? console.log(true) : console.log(false);
}
expect: {
t ? console.log(true) : console.log(false);
}
}
negate_iife_4: {
options = {
conditionals: true,
expression: true,
inline: true,
negate_iife: true,
sequences: true,
}
input: {
(function(){ return t })() ? console.log(true) : console.log(false);
(function(){
console.log("something");
})();
}
expect: {
t ? console.log(true) : console.log(false), void console.log("something");
}
}
negate_iife_5: {
options = {
conditionals: true,
expression: true,
inline: true,
negate_iife: true,
sequences: true,
}
input: {
if ((function(){ return t })()) {
foo(true);
} else {
bar(false);
}
(function(){
console.log("something");
})();
}
expect: {
t ? foo(true) : bar(false), void console.log("something");
}
}
negate_iife_5_off: {
options = {
conditionals: true,
expression: true,
inline: true,
negate_iife: false,
sequences: true,
};
input: {
if ((function(){ return t })()) {
foo(true);
} else {
bar(false);
}
(function(){
console.log("something");
})();
}
expect: {
t ? foo(true) : bar(false), void console.log("something");
}
}
issue_1254_negate_iife_true: {
options = {
expression: true,
inline: true,
negate_iife: true,
}
input: {
(function() {
return function() {
console.log('test')
};
})()();
}
expect_exact: 'void console.log("test");'
expect_stdout: true
}
issue_1254_negate_iife_nested: {
options = {
expression: true,
inline: true,
negate_iife: true,
}
input: {
(function() {
return function() {
console.log('test')
};
})()()()()();
}
expect_exact: '(void console.log("test"))()()();'
}
negate_iife_issue_1073: {
options = {
conditionals: true,
evaluate: true,
inline: true,
negate_iife: true,
reduce_vars: true,
sequences: true,
unused: true,
};
input: {
new (function(a) {
return function Foo() {
this.x = a;
console.log(this);
};
}(7))();
}
expect: {
new function() {
this.x = 7,
console.log(this);
}();
}
expect_stdout: true
}
issue_1288_side_effects: {
options = {
conditionals: true,
evaluate: true,
inline: true,
negate_iife: true,
reduce_vars: true,
side_effects: true,
unused: true,
};
input: {
if (w) ;
else {
(function f() {})();
}
if (!x) {
(function() {
x = {};
})();
}
if (y)
(function() {})();
else
(function(z) {
return z;
})(0);
}
expect: {
w;
x || (x = {});
y;
}
}
inner_var_for_in_1: {
options = {
evaluate: true,
inline: true,
reduce_vars: true,
}
input: {
function f() {
var a = 1, b = 2;
for (b in (function() {
return x(a, b, c);
})()) {
var c = 3, d = 4;
x(a, b, c, d);
}
x(a, b, c, d);
}
}
expect: {
function f() {
var a = 1, b = 2;
for (b in x(1, b, c)) {
var c = 3, d = 4;
x(1, b, c, d);
}
x(1, b, c, d);
}
}
}
issue_1595_3: {
options = {
evaluate: true,
inline: true,
passes: 2,
reduce_vars: true,
unused: true,
}
input: {
(function f(a) {
return g(a + 1);
})(2);
}
expect: {
g(3);
}
}
issue_1758: {
options = {
inline: true,
sequences: true,
side_effects: true,
}
input: {
console.log(function(c) {
var undefined = 42;
return function() {
c--;
c--, c.toString();
return;
}();
}());
}
expect: {
console.log(function(c) {
var undefined = 42;
return c--, c--, void c.toString();
}());
}
expect_stdout: "undefined"
}
wrap_iife: {
options = {
inline: true,
negate_iife: false,
}
beautify = {
wrap_iife: true,
}
input: {
(function() {
return function() {
console.log('test')
};
})()();
}
expect_exact: 'void console.log("test");'
}
wrap_iife_in_expression: {
options = {
inline: true,
negate_iife: false,
}
beautify = {
wrap_iife: true,
}
input: {
foo = (function () {
return bar();
})();
}
expect_exact: 'foo=bar();'
}
wrap_iife_in_return_call: {
options = {
inline: true,
negate_iife: false,
}
beautify = {
wrap_iife: true,
}
input: {
(function() {
return (function() {
console.log('test')
})();
})()();
}
expect_exact: '(void console.log("test"))();'
}
pure_annotation: {
options = {
inline: true,
side_effects: true,
}
input: {
/*@__PURE__*/(function() {
console.log("hello");
}());
}
expect_exact: ""
}
drop_fargs: {
options = {
cascade: true,
inline: true,
keep_fargs: false,
side_effects: true,
unused: true,
}
input: {
var a = 1;
!function(a_1) {
a++;
}(a++ + (a && a.var));
console.log(a);
}
expect: {
var a = 1;
!function() {
a++;
}(++a && a.var);
console.log(a);
}
expect_stdout: "3"
}
keep_fargs: {
options = {
cascade: true,
inline: true,
keep_fargs: true,
side_effects: true,
unused: true,
}
input: {
var a = 1;
!function(a_1) {
a++;
}(a++ + (a && a.var));
console.log(a);
}
expect: {
var a = 1;
!function(a_1) {
a++;
}(++a && a.var);
console.log(a);
}
expect_stdout: "3"
}

View File

@@ -22,7 +22,8 @@ negate_iife_1_off: {
negate_iife_2: { negate_iife_2: {
options = { options = {
negate_iife: true inline: true,
negate_iife: true,
}; };
input: { input: {
(function(){ return {} })().x = 10; (function(){ return {} })().x = 10;
@@ -32,6 +33,7 @@ negate_iife_2: {
negate_iife_2_side_effects: { negate_iife_2_side_effects: {
options = { options = {
inline: true,
negate_iife: true, negate_iife: true,
side_effects: true, side_effects: true,
} }
@@ -58,6 +60,7 @@ negate_iife_3_evaluate: {
options = { options = {
conditionals: true, conditionals: true,
evaluate: true, evaluate: true,
inline: true,
negate_iife: true, negate_iife: true,
} }
input: { input: {
@@ -100,6 +103,7 @@ negate_iife_3_off_evaluate: {
options = { options = {
conditionals: true, conditionals: true,
evaluate: true, evaluate: true,
inline: true,
negate_iife: false, negate_iife: false,
} }
input: { input: {

View File

@@ -0,0 +1,38 @@
eval_let_6: {
input: {
eval("let a;");
console.log();
}
expect: {
eval("let a;");
console.log();
}
expect_stdout: ""
node_version: ">=6"
}
eval_let_4: {
input: {
eval("let a;");
console.log();
}
expect: {
eval("let a;");
console.log();
}
expect_stdout: SyntaxError("Block-scoped declarations (let, const, function, class) not yet supported outside strict mode")
node_version: "4"
}
eval_let_0: {
input: {
eval("let a;");
console.log();
}
expect: {
eval("let a;");
console.log();
}
expect_stdout: SyntaxError("Unexpected identifier")
node_version: "<=0.12"
}

View File

@@ -555,3 +555,105 @@ native_prototype: {
"".indexOf.call(e, "bar"); "".indexOf.call(e, "bar");
} }
} }
accessor_boolean: {
input: {
var a = 1;
var b = {
get true() {
return a;
},
set false(c) {
a = c;
}
};
console.log(b.true, b.false = 2, b.true);
}
expect_exact: 'var a=1;var b={get true(){return a},set false(c){a=c}};console.log(b.true,b.false=2,b.true);'
expect_stdout: "1 2 2"
}
accessor_get_set: {
input: {
var a = 1;
var b = {
get set() {
return a;
},
set get(c) {
a = c;
}
};
console.log(b.set, b.get = 2, b.set);
}
expect_exact: 'var a=1;var b={get set(){return a},set get(c){a=c}};console.log(b.set,b.get=2,b.set);'
expect_stdout: "1 2 2"
}
accessor_null_undefined: {
input: {
var a = 1;
var b = {
get null() {
return a;
},
set undefined(c) {
a = c;
}
};
console.log(b.null, b.undefined = 2, b.null);
}
expect_exact: 'var a=1;var b={get null(){return a},set undefined(c){a=c}};console.log(b.null,b.undefined=2,b.null);'
expect_stdout: "1 2 2"
}
accessor_number: {
input: {
var a = 1;
var b = {
get 42() {
return a;
},
set 42(c) {
a = c;
}
};
console.log(b[42], b[42] = 2, b[42]);
}
expect_exact: 'var a=1;var b={get 42(){return a},set 42(c){a=c}};console.log(b[42],b[42]=2,b[42]);'
expect_stdout: "1 2 2"
}
accessor_string: {
input: {
var a = 1;
var b = {
get "a-b"() {
return a;
},
set "a-b"(c) {
a = c;
}
};
console.log(b["a-b"], b["a-b"] = 2, b["a-b"]);
}
expect_exact: 'var a=1;var b={get"a-b"(){return a},set"a-b"(c){a=c}};console.log(b["a-b"],b["a-b"]=2,b["a-b"]);'
expect_stdout: "1 2 2"
}
accessor_this: {
input: {
var a = 1;
var b = {
get this() {
return a;
},
set this(c) {
a = c;
}
};
console.log(b.this, b.this = 2, b.this);
}
expect_exact: 'var a=1;var b={get this(){return a},set this(c){a=c}};console.log(b.this,b.this=2,b.this);'
expect_stdout: "1 2 2"
}

View File

@@ -2,6 +2,7 @@ reduce_vars: {
options = { options = {
conditionals : true, conditionals : true,
evaluate : true, evaluate : true,
inline : true,
global_defs : { global_defs : {
C : 0 C : 0
}, },
@@ -1032,6 +1033,7 @@ defun_inline_2: {
defun_inline_3: { defun_inline_3: {
options = { options = {
evaluate: true, evaluate: true,
inline: true,
passes: 2, passes: 2,
reduce_vars: true, reduce_vars: true,
side_effects: true, side_effects: true,
@@ -1054,6 +1056,7 @@ defun_inline_3: {
defun_call: { defun_call: {
options = { options = {
inline: true,
reduce_vars: true, reduce_vars: true,
unused: true, unused: true,
} }
@@ -1080,6 +1083,7 @@ defun_call: {
defun_redefine: { defun_redefine: {
options = { options = {
inline: true,
reduce_vars: true, reduce_vars: true,
unused: true, unused: true,
} }
@@ -1112,6 +1116,7 @@ defun_redefine: {
func_inline: { func_inline: {
options = { options = {
inline: true,
reduce_vars: true, reduce_vars: true,
unused: true, unused: true,
} }
@@ -1138,6 +1143,7 @@ func_inline: {
func_modified: { func_modified: {
options = { options = {
inline: true,
reduce_vars: true, reduce_vars: true,
unused: true, unused: true,
} }
@@ -1311,19 +1317,47 @@ iife_func_side_effects: {
unused: true, unused: true,
} }
input: { input: {
function x() {
console.log("x");
}
function y() {
console.log("y");
}
function z() {
console.log("z");
}
(function(a, b, c) { (function(a, b, c) {
return b(); function y() {
console.log("FAIL");
}
return y + b();
})(x(), function() { })(x(), function() {
return y(); return y();
}, z()); }, z());
} }
expect: { expect: {
function x() {
console.log("x");
}
function y() {
console.log("y");
}
function z() {
console.log("z");
}
(function(a, b, c) { (function(a, b, c) {
return function() { return function() {
return y(); console.log("FAIL");
}(); } + b();
})(x(), 0, z()); })(x(), function() {
return y();
}, z());
} }
expect_stdout: [
"x",
"z",
"y",
]
} }
issue_1595_1: { issue_1595_1: {
@@ -1687,6 +1721,7 @@ redefine_arguments_1: {
redefine_arguments_2: { redefine_arguments_2: {
options = { options = {
evaluate: true, evaluate: true,
inline: true,
keep_fargs: false, keep_fargs: false,
reduce_vars: true, reduce_vars: true,
side_effects: true, side_effects: true,
@@ -1723,6 +1758,7 @@ redefine_arguments_2: {
redefine_arguments_3: { redefine_arguments_3: {
options = { options = {
evaluate: true, evaluate: true,
inline: true,
keep_fargs: false, keep_fargs: false,
passes: 3, passes: 3,
reduce_vars: true, reduce_vars: true,
@@ -1799,6 +1835,7 @@ redefine_farg_1: {
redefine_farg_2: { redefine_farg_2: {
options = { options = {
evaluate: true, evaluate: true,
inline: true,
keep_fargs: false, keep_fargs: false,
reduce_vars: true, reduce_vars: true,
side_effects: true, side_effects: true,
@@ -1835,6 +1872,7 @@ redefine_farg_2: {
redefine_farg_3: { redefine_farg_3: {
options = { options = {
evaluate: true, evaluate: true,
inline: true,
keep_fargs: false, keep_fargs: false,
passes: 3, passes: 3,
reduce_vars: true, reduce_vars: true,

14
test/compress/sandbox.js Normal file
View File

@@ -0,0 +1,14 @@
console_log: {
input: {
console.log("%% %s");
console.log("%% %s", "%s");
}
expect: {
console.log("%% %s");
console.log("%% %s", "%s");
}
expect_stdout: [
"%% %s",
"% %s",
]
}

View File

@@ -710,3 +710,23 @@ issue_27: {
})(jQuery); })(jQuery);
} }
} }
issue_2062: {
options = {
booleans: true,
cascade: true,
conditionals: true,
side_effects: true,
}
input: {
var a = 1;
if ([ a || a++ + a--, a++ + a--, a && a.var ]);
console.log(a);
}
expect: {
var a = 1;
a || (a++, a--), a++, --a && a.var;
console.log(a);
}
expect_stdout: "1"
}

View File

@@ -8,3 +8,12 @@ octal_escape_sequence: {
var border_check = "\x20\x30\x38\x30\x00\x30\xc0\x30"; var border_check = "\x20\x30\x38\x30\x00\x30\xc0\x30";
} }
} }
issue_1929: {
input: {
function f(s) {
return s.split(/[\\/]/);
}
}
expect_exact: "function f(s){return s.split(/[\\\\/]/)}"
}

31
test/fetch.js Normal file
View File

@@ -0,0 +1,31 @@
var fs = require("fs");
var path = require("path");
try {
fs.mkdirSync("./tmp");
} catch (e) {
if (e.code != "EEXIST") throw e;
}
function local(url) {
return path.join("./tmp", encodeURIComponent(url));
}
function read(url) {
return fs.createReadStream(local(url));
}
module.exports = function(url, callback) {
var result = read(url);
result.on("error", function(e) {
if (e.code != "ENOENT") return callback(e);
require(url.slice(0, url.indexOf(":"))).get(url, function(res) {
if (res.statusCode !== 200) return callback(res);
res.pipe(fs.createWriteStream(local(url)).on("close", function() {
callback(null, read(url));
}));
});
}).on("open", function() {
callback(null, result);
});
};

View File

@@ -0,0 +1 @@
if (0) else 1;

View File

@@ -0,0 +1 @@
return 42;

View File

@@ -0,0 +1 @@
console.log(x);

View File

@@ -0,0 +1 @@
{"version": 3,"sources": ["index.js"],"mappings": ";"}

View File

@@ -0,0 +1,5 @@
function test(callback) {
'aaaaaaaaaaaaaaaa';
callback(err, data);
callback(err, data);
}

View File

@@ -0,0 +1,5 @@
function test(a){
"aaaaaaaaaaaaaaaa"
;a(err,data),a(err,data)
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjAiXSwibmFtZXMiOlsidGVzdCIsImNhbGxiYWNrIiwiZXJyIiwiZGF0YSJdLCJtYXBwaW5ncyI6IkFBQUEsU0FBU0EsS0FBS0M7QUFDVjtDQUNBQSxFQUFTQyxJQUFLQyxNQUNkRixFQUFTQyxJQUFLQyJ9

View File

@@ -1,2 +1,2 @@
new function(){console.log(3)}; new function(){console.log(3)};
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0ZGluIl0sIm5hbWVzIjpbImNvbnNvbGUiLCJsb2ciXSwibWFwcGluZ3MiOiJBQUErQyxHQUFyQyxZQUFnQkEsUUFBUUMsSUFBSSIsInNvdXJjZXNDb250ZW50IjpbImNsYXNzIEZvbyB7IGNvbnN0cnVjdG9yKCl7Y29uc29sZS5sb2coMSsyKTt9IH0gbmV3IEZvbygpO1xuIl19 //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0ZGluIl0sIm5hbWVzIjpbImNvbnNvbGUiLCJsb2ciXSwibWFwcGluZ3MiOiJBQUErQyxJQUFyQyxXQUFnQkEsUUFBUUMsSUFBSSIsInNvdXJjZXNDb250ZW50IjpbImNsYXNzIEZvbyB7IGNvbnN0cnVjdG9yKCl7Y29uc29sZS5sb2coMSsyKTt9IH0gbmV3IEZvbygpO1xuIl19

View File

@@ -3,7 +3,7 @@
"use strict"; "use strict";
var site = "http://browserbench.org/JetStream/"; var site = "http://browserbench.org/JetStream";
if (typeof phantom == "undefined") { if (typeof phantom == "undefined") {
// workaround for tty output truncation upon process.exit() // workaround for tty output truncation upon process.exit()
[process.stdout, process.stderr].forEach(function(stream){ [process.stdout, process.stderr].forEach(function(stream){
@@ -11,45 +11,69 @@ if (typeof phantom == "undefined") {
stream._handle.setBlocking(true); stream._handle.setBlocking(true);
}); });
var args = process.argv.slice(2); var args = process.argv.slice(2);
var debug = args.indexOf("--debug");
if (debug >= 0) {
args.splice(debug, 1);
debug = true;
} else {
debug = false;
}
if (!args.length) { if (!args.length) {
args.push("-mc"); args.push("-mcb", "beautify=false,webkit");
} }
args.push("--stats"); args.push("--timings");
var child_process = require("child_process"); var child_process = require("child_process");
try { var fetch = require("./fetch");
require("phantomjs-prebuilt");
} catch(e) {
child_process.execSync("npm install phantomjs-prebuilt@2.1.14");
}
var http = require("http"); var http = require("http");
var server = http.createServer(function(request, response) { var server = http.createServer(function(request, response) {
request.resume(); request.resume();
var url = decodeURIComponent(request.url.slice(1)); var url = site + request.url;
var stderr = ""; fetch(url, function(err, res) {
var uglifyjs = child_process.fork("bin/uglifyjs", args, { if (err) throw err;
silent: true response.writeHead(200, {
}).on("exit", function(code) { "Content-Type": {
console.log("uglifyjs", url.indexOf(site) == 0 ? url.slice(site.length) : url, args.join(" ")); css: "text/css",
console.log(stderr); js: "application/javascript",
if (code) throw new Error("uglifyjs failed with code " + code); png: "image/png"
}); }[url.slice(url.lastIndexOf(".") + 1)] || "text/html; charset=utf-8"
uglifyjs.stderr.on("data", function(data) { });
stderr += data; if (/\.js$/.test(url)) {
}).setEncoding("utf8"); var stderr = "";
uglifyjs.stdout.pipe(response); var uglifyjs = child_process.fork("bin/uglifyjs", args, {
http.get(url, function(res) { silent: true
res.pipe(uglifyjs.stdin); }).on("exit", function(code) {
}); console.log("uglifyjs", url.slice(site.length + 1), args.join(" "));
}).listen().on("listening", function() { console.log(stderr);
var phantomjs = require("phantomjs-prebuilt"); if (code) throw new Error("uglifyjs failed with code " + code);
var program = phantomjs.exec(process.argv[1], server.address().port); });
program.stdout.pipe(process.stdout); uglifyjs.stderr.on("data", function(data) {
program.stderr.pipe(process.stderr); stderr += data;
program.on("exit", function(code) { }).setEncoding("utf8");
server.close(); uglifyjs.stdout.pipe(response);
if (code) throw new Error("JetStream failed!"); res.pipe(uglifyjs.stdin);
console.log("JetStream completed successfully."); } else {
res.pipe(response);
}
}); });
}).listen();
server.on("listening", function() {
var port = server.address().port;
if (debug) {
console.log("http://localhost:" + port + "/");
} else {
child_process.exec("npm install phantomjs-prebuilt@2.1.14 --no-save", function(error) {
if (error) throw error;
var program = require("phantomjs-prebuilt").exec(process.argv[1], port);
program.stdout.pipe(process.stdout);
program.stderr.pipe(process.stderr);
program.on("exit", function(code) {
server.close();
if (code) throw new Error("JetStream failed!");
console.log("JetStream completed successfully.");
process.exit(0);
});
});
}
}); });
server.timeout = 0; server.timeout = 0;
} else { } else {
@@ -63,10 +87,6 @@ if (typeof phantom == "undefined") {
phantom.exit(1); phantom.exit(1);
}; };
var url = "http://localhost:" + require("system").args[1] + "/"; var url = "http://localhost:" + require("system").args[1] + "/";
page.onResourceRequested = function(requestData, networkRequest) {
if (/\.js$/.test(requestData.url))
networkRequest.changeUrl(url + encodeURIComponent(requestData.url));
}
page.onConsoleMessage = function(msg) { page.onConsoleMessage = function(msg) {
if (/Error:/i.test(msg)) { if (/Error:/i.test(msg)) {
console.error(msg); console.error(msg);
@@ -77,8 +97,8 @@ if (typeof phantom == "undefined") {
phantom.exit(); phantom.exit();
} }
}; };
page.open(site, function(status) { page.open(url, function(status) {
if (status != "success") phantomjs.exit(1); if (status != "success") phantom.exit(1);
page.evaluate(function() { page.evaluate(function() {
JetStream.switchToQuick(); JetStream.switchToQuick();
JetStream.start(); JetStream.start();

View File

@@ -19,7 +19,9 @@ describe("bin/uglifyjs", function () {
eval(stdout); eval(stdout);
assert.strictEqual(typeof WrappedUglifyJS, 'object'); assert.strictEqual(typeof WrappedUglifyJS, 'object');
assert.strictEqual(WrappedUglifyJS.minify("foo([true,,2+3]);").code, "foo([!0,,5]);"); var result = WrappedUglifyJS.minify("foo([true,,2+3]);");
assert.strictEqual(result.error, undefined);
assert.strictEqual(result.code, "foo([!0,,5]);");
done(); done();
}); });
@@ -55,15 +57,15 @@ describe("bin/uglifyjs", function () {
}); });
}); });
it("Should append source map to output when using --source-map url=inline", function (done) { it("Should append source map to output when using --source-map url=inline", function (done) {
var command = uglifyjscmd + " test/input/issue-1323/sample.js --source-map url=inline"; var command = uglifyjscmd + " test/input/issue-1323/sample.js --source-map url=inline";
exec(command, function (err, stdout) { exec(command, function (err, stdout) {
if (err) throw err; if (err) throw err;
assert.strictEqual(stdout, "var bar=function(){function foo(bar){return bar}return foo}();\n" + assert.strictEqual(stdout, "var bar=function(){function foo(bar){return bar}return foo}();\n" +
"//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInRlc3QvaW5wdXQvaXNzdWUtMTMyMy9zYW1wbGUuanMiXSwibmFtZXMiOlsiYmFyIiwiZm9vIl0sIm1hcHBpbmdzIjoiQUFBQSxHQUFJQSxLQUFNLFdBQ04sUUFBU0MsS0FBS0QsS0FDVixNQUFPQSxLQUdYLE1BQU9DIn0=\n"); "//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInRlc3QvaW5wdXQvaXNzdWUtMTMyMy9zYW1wbGUuanMiXSwibmFtZXMiOlsiYmFyIiwiZm9vIl0sIm1hcHBpbmdzIjoiQUFBQSxJQUFJQSxJQUFNLFdBQ04sU0FBU0MsSUFBS0QsS0FDVixPQUFPQSxJQUdYLE9BQU9DIn0=\n");
done(); done();
}); });
}); });
it("should not append source map to output when not using --source-map url=inline", function (done) { it("should not append source map to output when not using --source-map url=inline", function (done) {
var command = uglifyjscmd + ' test/input/issue-1323/sample.js'; var command = uglifyjscmd + ' test/input/issue-1323/sample.js';
@@ -75,85 +77,102 @@ describe("bin/uglifyjs", function () {
done(); done();
}); });
}); });
it("should not consider source map file content as source map file name (issue #2082)", function (done) {
var command = [
uglifyjscmd,
"test/input/issue-2082/sample.js",
"--source-map", "content=test/input/issue-2082/sample.js.map",
"--source-map", "url=inline",
].join(" ");
exec(command, function (err, stdout, stderr) {
if (err) throw err;
var stderrLines = stderr.split('\n');
assert.strictEqual(stderrLines[0], 'INFO: Using input source map: test/input/issue-2082/sample.js.map');
assert.notStrictEqual(stderrLines[1], 'INFO: Using input source map: {"version": 3,"sources": ["index.js"],"mappings": ";"}');
done();
});
});
it("Should work with --keep-fnames (mangle only)", function (done) { it("Should work with --keep-fnames (mangle only)", function (done) {
var command = uglifyjscmd + ' test/input/issue-1431/sample.js --keep-fnames -m'; var command = uglifyjscmd + ' test/input/issue-1431/sample.js --keep-fnames -m';
exec(command, function (err, stdout) { exec(command, function (err, stdout) {
if (err) throw err; if (err) throw err;
assert.strictEqual(stdout, "function f(r){return function(){function n(n){return n*n}return r(n)}}function g(n){return n(1)+n(2)}console.log(f(g)()==5);\n"); assert.strictEqual(stdout, "function f(r){return function(){function n(n){return n*n}return r(n)}}function g(n){return n(1)+n(2)}console.log(f(g)()==5);\n");
done(); done();
}); });
}); });
it("Should work with --keep-fnames (mangle & compress)", function (done) { it("Should work with --keep-fnames (mangle & compress)", function (done) {
var command = uglifyjscmd + ' test/input/issue-1431/sample.js --keep-fnames -m -c unused=false'; var command = uglifyjscmd + ' test/input/issue-1431/sample.js --keep-fnames -m -c unused=false';
exec(command, function (err, stdout) { exec(command, function (err, stdout) {
if (err) throw err; if (err) throw err;
assert.strictEqual(stdout, "function f(r){return function(){function n(n){return n*n}return r(n)}}function g(n){return n(1)+n(2)}console.log(5==f(g)());\n"); assert.strictEqual(stdout, "function f(r){return function(){function n(n){return n*n}return r(n)}}function g(n){return n(1)+n(2)}console.log(5==f(g)());\n");
done(); done();
}); });
}); });
it("Should work with keep_fnames under mangler options", function (done) { it("Should work with keep_fnames under mangler options", function (done) {
var command = uglifyjscmd + ' test/input/issue-1431/sample.js -m keep_fnames=true'; var command = uglifyjscmd + ' test/input/issue-1431/sample.js -m keep_fnames=true';
exec(command, function (err, stdout) { exec(command, function (err, stdout) {
if (err) throw err; if (err) throw err;
assert.strictEqual(stdout, "function f(r){return function(){function n(n){return n*n}return r(n)}}function g(n){return n(1)+n(2)}console.log(f(g)()==5);\n"); assert.strictEqual(stdout, "function f(r){return function(){function n(n){return n*n}return r(n)}}function g(n){return n(1)+n(2)}console.log(f(g)()==5);\n");
done(); done();
}); });
}); });
it("Should work with --define (simple)", function (done) { it("Should work with --define (simple)", function (done) {
var command = uglifyjscmd + ' test/input/global_defs/simple.js --define D=5 -c'; var command = uglifyjscmd + ' test/input/global_defs/simple.js --define D=5 -c';
exec(command, function (err, stdout) { exec(command, function (err, stdout) {
if (err) throw err; if (err) throw err;
assert.strictEqual(stdout, "console.log(5);\n"); assert.strictEqual(stdout, "console.log(5);\n");
done(); done();
}); });
}); });
it("Should work with --define (nested)", function (done) { it("Should work with --define (nested)", function (done) {
var command = uglifyjscmd + ' test/input/global_defs/nested.js --define C.D=5,C.V=3 -c'; var command = uglifyjscmd + ' test/input/global_defs/nested.js --define C.D=5,C.V=3 -c';
exec(command, function (err, stdout) { exec(command, function (err, stdout) {
if (err) throw err; if (err) throw err;
assert.strictEqual(stdout, "console.log(3,5);\n"); assert.strictEqual(stdout, "console.log(3,5);\n");
done(); done();
}); });
}); });
it("Should work with --define (AST_Node)", function (done) { it("Should work with --define (AST_Node)", function (done) {
var command = uglifyjscmd + ' test/input/global_defs/simple.js --define console.log=stdout.println -c'; var command = uglifyjscmd + ' test/input/global_defs/simple.js --define console.log=stdout.println -c';
exec(command, function (err, stdout) { exec(command, function (err, stdout) {
if (err) throw err; if (err) throw err;
assert.strictEqual(stdout, "stdout.println(D);\n"); assert.strictEqual(stdout, "stdout.println(D);\n");
done(); done();
}); });
}); });
it("Should work with `--beautify`", function (done) { it("Should work with `--beautify`", function (done) {
var command = uglifyjscmd + ' test/input/issue-1482/input.js -b'; var command = uglifyjscmd + ' test/input/issue-1482/input.js -b';
exec(command, function (err, stdout) { exec(command, function (err, stdout) {
if (err) throw err; if (err) throw err;
assert.strictEqual(stdout, read("test/input/issue-1482/default.js")); assert.strictEqual(stdout, read("test/input/issue-1482/default.js"));
done(); done();
}); });
}); });
it("Should work with `--beautify bracketize`", function (done) { it("Should work with `--beautify bracketize`", function (done) {
var command = uglifyjscmd + ' test/input/issue-1482/input.js -b bracketize'; var command = uglifyjscmd + ' test/input/issue-1482/input.js -b bracketize';
exec(command, function (err, stdout) { exec(command, function (err, stdout) {
if (err) throw err; if (err) throw err;
assert.strictEqual(stdout, read("test/input/issue-1482/bracketize.js")); assert.strictEqual(stdout, read("test/input/issue-1482/bracketize.js"));
done(); done();
}); });
}); });
it("Should process inline source map", function(done) { it("Should process inline source map", function(done) {
var command = uglifyjscmd + " test/input/issue-520/input.js -mc toplevel --source-map content=inline,url=inline"; var command = uglifyjscmd + " test/input/issue-520/input.js -mc toplevel --source-map content=inline,url=inline";
@@ -173,7 +192,7 @@ describe("bin/uglifyjs", function () {
assert.strictEqual(stdout, [ assert.strictEqual(stdout, [
"var bar=function(){function foo(bar){return bar}return foo}();", "var bar=function(){function foo(bar){return bar}return foo}();",
"//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInRlc3QvaW5wdXQvaXNzdWUtMTMyMy9zYW1wbGUuanMiXSwibmFtZXMiOlsiYmFyIiwiZm9vIl0sIm1hcHBpbmdzIjoiQUFBQSxHQUFJQSxLQUFNLFdBQ04sUUFBU0MsS0FBS0QsS0FDVixNQUFPQSxLQUdYLE1BQU9DIn0=", "//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInRlc3QvaW5wdXQvaXNzdWUtMTMyMy9zYW1wbGUuanMiXSwibmFtZXMiOlsiYmFyIiwiZm9vIl0sIm1hcHBpbmdzIjoiQUFBQSxJQUFJQSxJQUFNLFdBQ04sU0FBU0MsSUFBS0QsS0FDVixPQUFPQSxJQUdYLE9BQU9DIn0=",
"", "",
].join("\n")); ].join("\n"));
assert.strictEqual(stderr, "WARN: inline source map not found\n"); assert.strictEqual(stderr, "WARN: inline source map not found\n");
@@ -260,214 +279,244 @@ describe("bin/uglifyjs", function () {
}); });
}); });
it("Should throw syntax error (5--)", function(done) { it("Should throw syntax error (5--)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/assign_1.js'; var command = uglifyjscmd + ' test/input/invalid/assign_1.js';
exec(command, function (err, stdout, stderr) { exec(command, function (err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [ assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/assign_1.js:1,18", "Parse error at test/input/invalid/assign_1.js:1,18",
"console.log(1 || 5--);", "console.log(1 || 5--);",
" ^", " ^",
"ERROR: Invalid use of -- operator" "ERROR: Invalid use of -- operator"
].join("\n")); ].join("\n"));
done(); done();
}); });
}); });
it("Should throw syntax error (Math.random() /= 2)", function(done) { it("Should throw syntax error (Math.random() /= 2)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/assign_2.js'; var command = uglifyjscmd + ' test/input/invalid/assign_2.js';
exec(command, function (err, stdout, stderr) { exec(command, function (err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [ assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/assign_2.js:1,32", "Parse error at test/input/invalid/assign_2.js:1,32",
"console.log(2 || (Math.random() /= 2));", "console.log(2 || (Math.random() /= 2));",
" ^", " ^",
"ERROR: Invalid assignment" "ERROR: Invalid assignment"
].join("\n")); ].join("\n"));
done(); done();
}); });
}); });
it("Should throw syntax error (++this)", function(done) { it("Should throw syntax error (++this)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/assign_3.js'; var command = uglifyjscmd + ' test/input/invalid/assign_3.js';
exec(command, function (err, stdout, stderr) { exec(command, function (err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [ assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/assign_3.js:1,17", "Parse error at test/input/invalid/assign_3.js:1,17",
"console.log(3 || ++this);", "console.log(3 || ++this);",
" ^", " ^",
"ERROR: Invalid use of ++ operator" "ERROR: Invalid use of ++ operator"
].join("\n")); ].join("\n"));
done(); done();
}); });
}); });
it("Should throw syntax error (++null)", function(done) { it("Should throw syntax error (++null)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/assign_4.js'; var command = uglifyjscmd + ' test/input/invalid/assign_4.js';
exec(command, function (err, stdout, stderr) { exec(command, function (err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [ assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/assign_4.js:1,0", "Parse error at test/input/invalid/assign_4.js:1,0",
"++null", "++null",
"^", "^",
"ERROR: Invalid use of ++ operator" "ERROR: Invalid use of ++ operator"
].join("\n")); ].join("\n"));
done(); done();
}); });
}); });
it("Should throw syntax error (a.=)", function(done) { it("Should throw syntax error (a.=)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/dot_1.js'; var command = uglifyjscmd + ' test/input/invalid/dot_1.js';
exec(command, function (err, stdout, stderr) { exec(command, function (err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [ assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/dot_1.js:1,2", "Parse error at test/input/invalid/dot_1.js:1,2",
"a.=", "a.=",
" ^", " ^",
"ERROR: Unexpected token: operator (=)" "ERROR: Unexpected token: operator (=)"
].join("\n")); ].join("\n"));
done(); done();
}); });
}); });
it("Should throw syntax error (%.a)", function(done) { it("Should throw syntax error (%.a)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/dot_2.js'; var command = uglifyjscmd + ' test/input/invalid/dot_2.js';
exec(command, function (err, stdout, stderr) { exec(command, function (err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [ assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/dot_2.js:1,0", "Parse error at test/input/invalid/dot_2.js:1,0",
"%.a;", "%.a;",
"^", "^",
"ERROR: Unexpected token: operator (%)" "ERROR: Unexpected token: operator (%)"
].join("\n")); ].join("\n"));
done(); done();
}); });
}); });
it("Should throw syntax error (a./();)", function(done) { it("Should throw syntax error (a./();)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/dot_3.js'; var command = uglifyjscmd + ' test/input/invalid/dot_3.js';
exec(command, function (err, stdout, stderr) { exec(command, function (err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [ assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/dot_3.js:1,2", "Parse error at test/input/invalid/dot_3.js:1,2",
"a./();", "a./();",
" ^", " ^",
"ERROR: Unexpected token: operator (/)" "ERROR: Unexpected token: operator (/)"
].join("\n")); ].join("\n"));
done(); done();
}); });
}); });
it("Should throw syntax error ({%: 1})", function(done) { it("Should throw syntax error ({%: 1})", function(done) {
var command = uglifyjscmd + ' test/input/invalid/object.js'; var command = uglifyjscmd + ' test/input/invalid/object.js';
exec(command, function (err, stdout, stderr) { exec(command, function (err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [ assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/object.js:1,13", "Parse error at test/input/invalid/object.js:1,13",
"console.log({%: 1});", "console.log({%: 1});",
" ^", " ^",
"ERROR: Unexpected token: operator (%)" "ERROR: Unexpected token: operator (%)"
].join("\n")); ].join("\n"));
done(); done();
}); });
}); });
it("Should throw syntax error (delete x)", function(done) { it("Should throw syntax error (delete x)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/delete.js'; var command = uglifyjscmd + ' test/input/invalid/delete.js';
exec(command, function (err, stdout, stderr) { exec(command, function (err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [ assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/delete.js:13,11", "Parse error at test/input/invalid/delete.js:13,11",
" delete x;", " delete x;",
" ^", " ^",
"ERROR: Calling delete on expression not allowed in strict mode" "ERROR: Calling delete on expression not allowed in strict mode"
].join("\n")); ].join("\n"));
done(); done();
}); });
}); });
it("Should throw syntax error (function g(arguments))", function(done) { it("Should throw syntax error (function g(arguments))", function(done) {
var command = uglifyjscmd + ' test/input/invalid/function_1.js'; var command = uglifyjscmd + ' test/input/invalid/function_1.js';
exec(command, function (err, stdout, stderr) { exec(command, function (err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [ assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/function_1.js:4,11", "Parse error at test/input/invalid/function_1.js:4,11",
"function g(arguments) {", "function g(arguments) {",
" ^", " ^",
"ERROR: Unexpected arguments in strict mode" "ERROR: Unexpected arguments in strict mode"
].join("\n")); ].join("\n"));
done(); done();
}); });
}); });
it("Should throw syntax error (function eval())", function(done) { it("Should throw syntax error (function eval())", function(done) {
var command = uglifyjscmd + ' test/input/invalid/function_2.js'; var command = uglifyjscmd + ' test/input/invalid/function_2.js';
exec(command, function (err, stdout, stderr) { exec(command, function (err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [ assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/function_2.js:4,9", "Parse error at test/input/invalid/function_2.js:4,9",
"function eval() {", "function eval() {",
" ^", " ^",
"ERROR: Unexpected eval in strict mode" "ERROR: Unexpected eval in strict mode"
].join("\n")); ].join("\n"));
done(); done();
}); });
}); });
it("Should throw syntax error (iife arguments())", function(done) { it("Should throw syntax error (iife arguments())", function(done) {
var command = uglifyjscmd + ' test/input/invalid/function_3.js'; var command = uglifyjscmd + ' test/input/invalid/function_3.js';
exec(command, function (err, stdout, stderr) { exec(command, function (err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [ assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/function_3.js:4,10", "Parse error at test/input/invalid/function_3.js:4,10",
"!function arguments() {", "!function arguments() {",
" ^", " ^",
"ERROR: Unexpected arguments in strict mode" "ERROR: Unexpected arguments in strict mode"
].join("\n")); ].join("\n"));
done(); done();
}); });
}); });
it("Should throw syntax error (catch(eval))", function(done) { it("Should throw syntax error (catch(eval))", function(done) {
var command = uglifyjscmd + ' test/input/invalid/try.js'; var command = uglifyjscmd + ' test/input/invalid/try.js';
exec(command, function (err, stdout, stderr) { exec(command, function (err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [ assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/try.js:7,18", "Parse error at test/input/invalid/try.js:7,18",
" try {} catch (eval) {}", " try {} catch (eval) {}",
" ^", " ^",
"ERROR: Unexpected eval in strict mode" "ERROR: Unexpected eval in strict mode"
].join("\n")); ].join("\n"));
done(); done();
}); });
}); });
it("Should throw syntax error (var eval)", function(done) { it("Should throw syntax error (var eval)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/var.js'; var command = uglifyjscmd + ' test/input/invalid/var.js';
exec(command, function (err, stdout, stderr) { exec(command, function (err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [ assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/var.js:7,8", "Parse error at test/input/invalid/var.js:7,8",
" var eval;", " var eval;",
" ^", " ^",
"ERROR: Unexpected eval in strict mode" "ERROR: Unexpected eval in strict mode"
].join("\n")); ].join("\n"));
done(); done();
}); });
});
it("Should throw syntax error (else)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/else.js';
exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/else.js:1,7",
"if (0) else 1;",
" ^",
"ERROR: Unexpected token: keyword (else)"
].join("\n"));
done();
});
});
it("Should throw syntax error (return)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/return.js';
exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/return.js:1,0",
"return 42;",
"^",
"ERROR: 'return' outside of function"
].join("\n"));
done();
});
}); });
it("Should handle literal string as source map input", function(done) { it("Should handle literal string as source map input", function(done) {
var command = [ var command = [
@@ -482,7 +531,7 @@ describe("bin/uglifyjs", function () {
assert.strictEqual(stdout, [ assert.strictEqual(stdout, [
'"use strict";var foo=function foo(x){return"foo "+x};console.log(foo("bar"));', '"use strict";var foo=function foo(x){return"foo "+x};console.log(foo("bar"));',
"//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImluZGV4LmpzIl0sIm5hbWVzIjpbImZvbyIsIngiLCJjb25zb2xlIiwibG9nIl0sIm1hcHBpbmdzIjoiWUFBQSxJQUFJQSxLQUFNLFFBQU5BLEtBQU1DLEdBQUEsTUFBSyxPQUFTQSxFQUN4QkMsU0FBUUMsSUFBSUgsSUFBSSJ9", "//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImluZGV4LmpzIl0sIm5hbWVzIjpbImZvbyIsIngiLCJjb25zb2xlIiwibG9nIl0sIm1hcHBpbmdzIjoiYUFBQSxJQUFJQSxJQUFNLFNBQU5BLElBQU1DLEdBQUEsTUFBSyxPQUFTQSxHQUN4QkMsUUFBUUMsSUFBSUgsSUFBSSJ9",
"" ""
].join("\n")); ].join("\n"));
done(); done();
@@ -505,4 +554,33 @@ describe("bin/uglifyjs", function () {
done(); done();
}); });
}); });
it("Should print supported options on invalid option syntax", function(done) {
var command = uglifyjscmd + " test/input/comments/filter.js -b ascii-only";
exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.ok(/^Supported options:\n[\s\S]*?\nERROR: `ascii-only` is not a supported option/.test(stderr), stderr);
done();
});
});
it("Should work with --mangle reserved=[]", function (done) {
var command = uglifyjscmd + ' test/input/issue-505/input.js -m reserved=[callback]';
exec(command, function (err, stdout) {
if (err) throw err;
assert.strictEqual(stdout, 'function test(callback){"aaaaaaaaaaaaaaaa";callback(err,data);callback(err,data)}\n');
done();
});
});
it("Should work with --mangle reserved=false", function (done) {
var command = uglifyjscmd + ' test/input/issue-505/input.js -m reserved=false';
exec(command, function (err, stdout) {
if (err) throw err;
assert.strictEqual(stdout, 'function test(a){"aaaaaaaaaaaaaaaa";a(err,data);a(err,data)}\n');
done();
});
});
}); });

View File

@@ -31,7 +31,7 @@ describe("bin/uglifyjs with input file globs", function() {
exec(command, function(err, stdout) { exec(command, function(err, stdout) {
if (err) throw err; if (err) throw err;
assert.strictEqual(stdout, 'var print=console.log.bind(console),a=function(n){return 3*n}(3),b=function(n){return n/2}(12);print("qux",a,b),function(n){print("Foo:",2*n)}(11);\n'); assert.strictEqual(stdout, 'var print=console.log.bind(console);print("qux",9,6),print("Foo:",2*11);\n');
done(); done();
}); });
}); });

View File

@@ -13,6 +13,13 @@ describe("minify", function() {
assert.strictEqual(result.code, 'function foo(n){return n?3:7}'); assert.strictEqual(result.code, 'function foo(n){return n?3:7}');
}); });
it("Should skip inherited keys from `files`", function() {
var files = Object.create({ skip: this });
files[0] = "alert(1 + 1)";
var result = Uglify.minify(files);
assert.strictEqual(result.code, "alert(2);");
});
describe("keep_quoted_props", function() { describe("keep_quoted_props", function() {
it("Should preserve quotes in object literals", function() { it("Should preserve quotes in object literals", function() {
var js = 'var foo = {"x": 1, y: 2, \'z\': 3};'; var js = 'var foo = {"x": 1, y: 2, \'z\': 3};';
@@ -106,7 +113,7 @@ describe("minify", function() {
content: "inline" content: "inline"
} }
}); });
assert.strictEqual(result.code, "var bar=function(){function foo(bar){return bar}return foo}();"); assert.strictEqual(result.code, "var bar=function(){return function(bar){return bar}}();");
assert.strictEqual(warnings.length, 1); assert.strictEqual(warnings.length, 1);
assert.strictEqual(warnings[0], "inline source map not found"); assert.strictEqual(warnings[0], "inline source map not found");
} finally { } finally {
@@ -138,13 +145,25 @@ describe("minify", function() {
}); });
var code = result.code; var code = result.code;
assert.strictEqual(code, "var a=function(n){return n};\n" + assert.strictEqual(code, "var a=function(n){return n};\n" +
"//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjAiXSwibmFtZXMiOlsiYSIsImZvbyJdLCJtYXBwaW5ncyI6IkFBQUEsR0FBSUEsR0FBSSxTQUFTQyxHQUFPLE1BQU9BIn0="); "//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjAiXSwibmFtZXMiOlsiYSIsImZvbyJdLCJtYXBwaW5ncyI6IkFBQUEsSUFBSUEsRUFBSSxTQUFTQyxHQUFPLE9BQU9BIn0=");
}); });
it("should not append source map to output js when sourceMapInline is not enabled", function() { it("should not append source map to output js when sourceMapInline is not enabled", function() {
var result = Uglify.minify('var a = function(foo) { return foo; };'); var result = Uglify.minify('var a = function(foo) { return foo; };');
var code = result.code; var code = result.code;
assert.strictEqual(code, "var a=function(n){return n};"); assert.strictEqual(code, "var a=function(n){return n};");
}); });
it("should work with max_line_len", function() {
var result = Uglify.minify(read("./test/input/issue-505/input.js"), {
output: {
max_line_len: 20
},
sourceMap: {
url: "inline"
}
});
assert.strictEqual(result.error, undefined);
assert.strictEqual(result.code, read("./test/input/issue-505/output.js"));
});
}); });
describe("#__PURE__", function() { describe("#__PURE__", function() {
@@ -181,4 +200,31 @@ describe("minify", function() {
assert.strictEqual(err.col, 12); assert.strictEqual(err.col, 12);
}); });
}); });
describe("global_defs", function() {
it("should throw for non-trivial expressions", function() {
var result = Uglify.minify("alert(42);", {
compress: {
global_defs: {
"@alert": "debugger"
}
}
});
var err = result.error;
assert.ok(err instanceof Error);
assert.strictEqual(err.stack.split(/\n/)[0], "Error: Can't handle expression: debugger");
});
it("should skip inherited properties", function() {
var foo = Object.create({ skip: this });
foo.bar = 42;
var result = Uglify.minify("alert(FOO);", {
compress: {
global_defs: {
FOO: foo
}
}
});
assert.strictEqual(result.code, "alert({bar:42});");
});
});
}); });

View File

@@ -1,16 +1,13 @@
var assert = require("assert"); var assert = require("assert");
var semver = require("semver");
var spawn = require("child_process").spawn; var spawn = require("child_process").spawn;
if (!process.env.UGLIFYJS_TEST_ALL) return; if (!process.env.UGLIFYJS_TEST_ALL) return;
function run(command, args, done) { function run(command, args, done) {
var id = setInterval(function() {
process.stdout.write("\0");
}, 5 * 60 * 1000);
spawn(command, args, { spawn(command, args, {
stdio: "ignore" stdio: [ "ignore", 1, 2 ]
}).on("exit", function(code) { }).on("exit", function(code) {
clearInterval(id);
assert.strictEqual(code, 0); assert.strictEqual(code, 0);
done(); done();
}); });
@@ -36,11 +33,9 @@ describe("test/benchmark.js", function() {
}); });
}); });
if (semver.satisfies(process.version, "0.12")) return;
describe("test/jetstream.js", function() { describe("test/jetstream.js", function() {
this.timeout(20 * 60 * 1000); this.timeout(20 * 60 * 1000);
it("Should install phantomjs-prebuilt", function(done) {
run("npm", ["install", "phantomjs-prebuilt@2.1.14"], done);
});
[ [
"-mc", "-mc",
"-mc keep_fargs=false,passes=3,pure_getters,unsafe,unsafe_comps,unsafe_math,unsafe_proto", "-mc keep_fargs=false,passes=3,pure_getters,unsafe,unsafe_comps,unsafe_math,unsafe_proto",
@@ -48,6 +43,7 @@ describe("test/jetstream.js", function() {
it("Should pass with options " + options, function(done) { it("Should pass with options " + options, function(done) {
var args = options.split(/ /); var args = options.split(/ /);
args.unshift("test/jetstream.js"); args.unshift("test/jetstream.js");
args.push("-b", "beautify=false,webkit");
run(process.argv[0], args, done); run(process.argv[0], args, done);
}); });
}); });

View File

@@ -15,7 +15,9 @@ describe("spidermonkey export/import sanity test", function() {
eval(stdout); eval(stdout);
assert.strictEqual(typeof SpiderUglify, "object"); assert.strictEqual(typeof SpiderUglify, "object");
assert.strictEqual(SpiderUglify.minify("foo([true,,2+3]);").code, "foo([!0,,5]);"); var result = SpiderUglify.minify("foo([true,,2+3]);");
assert.strictEqual(result.error, undefined);
assert.strictEqual(result.code, "foo([!0,,5]);");
done(); done();
}); });

View File

@@ -5,6 +5,7 @@ var path = require("path");
var fs = require("fs"); var fs = require("fs");
var assert = require("assert"); var assert = require("assert");
var sandbox = require("./sandbox"); var sandbox = require("./sandbox");
var semver = require("semver");
var tests_dir = path.dirname(module.filename); var tests_dir = path.dirname(module.filename);
var failures = 0; var failures = 0;
@@ -164,7 +165,8 @@ function run_compress_tests() {
failed_files[file] = 1; failed_files[file] = 1;
} }
} }
if (test.expect_stdout) { if (test.expect_stdout
&& (!test.node_version || semver.satisfies(process.version, test.node_version))) {
var stdout = sandbox.run_code(input_code); var stdout = sandbox.run_code(input_code);
if (test.expect_stdout === true) { if (test.expect_stdout === true) {
test.expect_stdout = stdout; test.expect_stdout = stdout;
@@ -274,7 +276,14 @@ function parse_test(file) {
if (node instanceof U.AST_LabeledStatement) { if (node instanceof U.AST_LabeledStatement) {
var label = node.label; var label = node.label;
assert.ok( assert.ok(
["input", "expect", "expect_exact", "expect_warnings", "expect_stdout"].indexOf(label.name) >= 0, [
"input",
"expect",
"expect_exact",
"expect_warnings",
"expect_stdout",
"node_version",
].indexOf(label.name) >= 0,
tmpl("Unsupported label {name} [{line},{col}]", { tmpl("Unsupported label {name} [{line},{col}]", {
name: label.name, name: label.name,
line: label.start.line, line: label.start.line,
@@ -282,11 +291,27 @@ function parse_test(file) {
}) })
); );
var stat = node.body; var stat = node.body;
if (label.name == "expect_exact") { if (label.name == "expect_exact" || label.name == "node_version") {
test[label.name] = read_string(stat); test[label.name] = read_string(stat);
} else if (label.name == "expect_stdout") { } else if (label.name == "expect_stdout") {
if (stat.TYPE == "SimpleStatement" && stat.body instanceof U.AST_Boolean) { if (stat.TYPE == "SimpleStatement") {
test[label.name] = stat.body.value; var body = stat.body;
if (body instanceof U.AST_Boolean) {
test[label.name] = body.value;
} else if (body instanceof U.AST_Call) {
var ctor = global[body.expression.name];
assert.ok(ctor === Error || ctor.prototype instanceof Error, tmpl("Unsupported expect_stdout format [{line},{col}]", {
line: label.start.line,
col: label.start.col
}));
test[label.name] = ctor.apply(null, body.args.map(function(node) {
assert.ok(node instanceof U.AST_Constant, tmpl("Unsupported expect_stdout format [{line},{col}]", {
line: label.start.line,
col: label.start.col
}));
return node.value;
}));
}
} else { } else {
test[label.name] = read_string(stat) + "\n"; test[label.name] = read_string(stat) + "\n";
} }

View File

@@ -1,3 +1,4 @@
var semver = require("semver");
var vm = require("vm"); var vm = require("vm");
function safe_log(arg, level) { function safe_log(arg, level) {
@@ -18,23 +19,25 @@ function safe_log(arg, level) {
var FUNC_TOSTRING = [ var FUNC_TOSTRING = [
"Function.prototype.toString = Function.prototype.valueOf = function() {", "Function.prototype.toString = Function.prototype.valueOf = function() {",
" var id = 0;", " var id = 100000;",
" return function() {", " return function() {",
' if (this === Array) return "[Function: Array]";', ' if (this === Array) return "[Function: Array]";',
' if (this === Object) return "[Function: Object]";', ' if (this === Object) return "[Function: Object]";',
" var i = this.name;", " var i = this.name;",
' if (typeof i != "number") {', ' if (typeof i != "number") {',
" i = ++id;", " i = ++id;",
].concat(Object.getOwnPropertyDescriptor(Function.prototype, "name").configurable ? [
' Object.defineProperty(this, "name", {', ' Object.defineProperty(this, "name", {',
" get: function() {", " get: function() {",
" return i;", " return i;",
" }", " }",
" });", " });",
] : [], [
" }", " }",
' return "[Function: " + i + "]";', ' return "[Function: " + i + "]";',
" }", " }",
"}();", "}();",
].join("\n"); ]).join("\n");
exports.run_code = function(code) { exports.run_code = function(code) {
var stdout = ""; var stdout = "";
var original_write = process.stdout.write; var original_write = process.stdout.write;
@@ -49,7 +52,10 @@ exports.run_code = function(code) {
"}();", "}();",
].join("\n"), { ].join("\n"), {
console: { console: {
log: function() { log: function(msg) {
if (arguments.length == 1 && typeof msg == "string") {
return console.log("%s", msg);
}
return console.log.apply(console, [].map.call(arguments, function(arg) { return console.log.apply(console, [].map.call(arguments, function(arg) {
return safe_log(arg, 3); return safe_log(arg, 3);
})); }));
@@ -63,7 +69,7 @@ exports.run_code = function(code) {
process.stdout.write = original_write; process.stdout.write = original_write;
} }
}; };
exports.same_stdout = ~process.version.lastIndexOf("v0.12.", 0) ? function(expected, actual) { exports.same_stdout = semver.satisfies(process.version, "0.12") ? function(expected, actual) {
if (typeof expected != typeof actual) return false; if (typeof expected != typeof actual) return false;
if (typeof expected != "string") { if (typeof expected != "string") {
if (expected.name != actual.name) return false; if (expected.name != actual.name) return false;

View File

@@ -1,4 +1,4 @@
var UglifyJS = require("./node"); var UglifyJS = require("..");
var ok = require("assert"); var ok = require("assert");
module.exports = function () { module.exports = function () {
@@ -26,11 +26,11 @@ module.exports = function () {
} }
function source_map(js) { function source_map(js) {
var source_map = UglifyJS.SourceMap(); return JSON.parse(UglifyJS.minify(js, {
var stream = UglifyJS.OutputStream({ source_map: source_map }); compress: false,
var parsed = UglifyJS.parse(js); mangle: false,
parsed.print(stream); sourceMap: true
return JSON.parse(source_map.toString()); }).map);
} }
// Run standalone // Run standalone

View File

@@ -12,7 +12,7 @@
stream._handle.setBlocking(true); stream._handle.setBlocking(true);
}); });
var UglifyJS = require("./node"); var UglifyJS = require("..");
var randomBytes = require("crypto").randomBytes; var randomBytes = require("crypto").randomBytes;
var sandbox = require("./sandbox"); var sandbox = require("./sandbox");
@@ -102,23 +102,23 @@ for (var i = 2; i < process.argv.length; ++i) {
case '--help': case '--help':
case '-h': case '-h':
case '-?': case '-?':
console.log('** UglifyJS fuzzer help **'); println('** UglifyJS fuzzer help **');
console.log('Valid options (optional):'); println('Valid options (optional):');
console.log('<number>: generate this many cases (if used must be first arg)'); println('<number>: generate this many cases (if used must be first arg)');
console.log('-v: print every generated test case'); println('-v: print every generated test case');
console.log('-V: print every 100th generated test case'); println('-V: print every 100th generated test case');
console.log('-t <int>: generate this many toplevels per run (more take longer)'); println('-t <int>: generate this many toplevels per run (more take longer)');
console.log('-r <int>: maximum recursion depth for generator (higher takes longer)'); println('-r <int>: maximum recursion depth for generator (higher takes longer)');
console.log('-s1 <statement name>: force the first level statement to be this one (see list below)'); println('-s1 <statement name>: force the first level statement to be this one (see list below)');
console.log('-s2 <statement name>: force the second level statement to be this one (see list below)'); println('-s2 <statement name>: force the second level statement to be this one (see list below)');
console.log('--no-catch-redef: do not redefine catch variables'); println('--no-catch-redef: do not redefine catch variables');
console.log('--no-directive: do not generate directives'); println('--no-directive: do not generate directives');
console.log('--use-strict: generate "use strict"'); println('--use-strict: generate "use strict"');
console.log('--stmt-depth-from-func: reset statement depth counter at each function, counts from global otherwise'); println('--stmt-depth-from-func: reset statement depth counter at each function, counts from global otherwise');
console.log('--only-stmt <statement names>: a comma delimited white list of statements that may be generated'); println('--only-stmt <statement names>: a comma delimited white list of statements that may be generated');
console.log('--without-stmt <statement names>: a comma delimited black list of statements never to generate'); println('--without-stmt <statement names>: a comma delimited black list of statements never to generate');
console.log('List of accepted statement names: ' + Object.keys(STMT_ARG_TO_ID)); println('List of accepted statement names: ' + Object.keys(STMT_ARG_TO_ID));
console.log('** UglifyJS fuzzer exiting **'); println('** UglifyJS fuzzer exiting **');
return 0; return 0;
default: default:
// first arg may be a number. // first arg may be a number.
@@ -941,7 +941,17 @@ if (require.main !== module) {
return; return;
} }
function try_beautify(code, result) { function println(msg) {
if (typeof msg != "undefined") process.stdout.write(msg);
process.stdout.write("\n");
}
function errorln(msg) {
if (typeof msg != "undefined") process.stderr.write(msg);
process.stderr.write("\n");
}
function try_beautify(code, result, printfn) {
var beautified = UglifyJS.minify(code, { var beautified = UglifyJS.minify(code, {
compress: false, compress: false,
mangle: false, mangle: false,
@@ -951,51 +961,34 @@ function try_beautify(code, result) {
}, },
}); });
if (beautified.error) { if (beautified.error) {
console.log("// !!! beautify failed !!!"); printfn("// !!! beautify failed !!!");
console.log(beautified.error.stack); printfn(beautified.error.stack);
} else if (sandbox.same_stdout(sandbox.run_code(beautified.code), result)) { } else if (sandbox.same_stdout(sandbox.run_code(beautified.code), result)) {
console.log("// (beautified)"); printfn("// (beautified)");
console.log(beautified.code); printfn(beautified.code);
return; return;
} }
console.log("//"); printfn("//");
console.log(code); printfn(code);
} }
function infer_options(ctor) { var default_options = UglifyJS.default_options();
try {
ctor({ 0: 0 });
} catch (e) {
return e.defs;
}
}
var default_options = {
compress: infer_options(UglifyJS.Compressor),
mangle: {
"cache": null,
"eval": false,
"ie8": false,
"keep_fnames": false,
"toplevel": false,
},
output: infer_options(UglifyJS.OutputStream),
};
function log_suspects(minify_options, component) { function log_suspects(minify_options, component) {
var options = component in minify_options ? minify_options[component] : true; var options = component in minify_options ? minify_options[component] : true;
if (!options) return; if (!options) return;
options = UglifyJS.defaults(options, default_options[component]); if (typeof options != "object") options = {};
var suspects = Object.keys(default_options[component]).filter(function(name) { var defs = default_options[component];
if (options[name]) { var suspects = Object.keys(defs).filter(function(name) {
if ((name in options ? options : defs)[name]) {
var m = JSON.parse(JSON.stringify(minify_options)); var m = JSON.parse(JSON.stringify(minify_options));
var o = JSON.parse(JSON.stringify(options)); var o = JSON.parse(JSON.stringify(options));
o[name] = false; o[name] = false;
m[component] = o; m[component] = o;
var result = UglifyJS.minify(original_code, m); var result = UglifyJS.minify(original_code, m);
if (result.error) { if (result.error) {
console.log("Error testing options." + component + "." + name); errorln("Error testing options." + component + "." + name);
console.log(result.error); errorln(result.error.stack);
} else { } else {
var r = sandbox.run_code(result.code); var r = sandbox.run_code(result.code);
return sandbox.same_stdout(original_result, r); return sandbox.same_stdout(original_result, r);
@@ -1003,49 +996,49 @@ function log_suspects(minify_options, component) {
} }
}); });
if (suspects.length > 0) { if (suspects.length > 0) {
console.log("Suspicious", component, "options:"); errorln("Suspicious " + component + " options:");
suspects.forEach(function(name) { suspects.forEach(function(name) {
console.log(" " + name); errorln(" " + name);
}); });
console.log(); errorln();
} }
} }
function log(options) { function log(options) {
if (!ok) console.log('\n\n\n\n\n\n!!!!!!!!!!\n\n\n'); if (!ok) errorln('\n\n\n\n\n\n!!!!!!!!!!\n\n\n');
console.log("//============================================================="); errorln("//=============================================================");
if (!ok) console.log("// !!!!!! Failed... round", round); if (!ok) errorln("// !!!!!! Failed... round " + round);
console.log("// original code"); errorln("// original code");
try_beautify(original_code, original_result); try_beautify(original_code, original_result, errorln);
console.log(); errorln();
console.log(); errorln();
console.log("//-------------------------------------------------------------"); errorln("//-------------------------------------------------------------");
if (typeof uglify_code == "string") { if (typeof uglify_code == "string") {
console.log("// uglified code"); errorln("// uglified code");
try_beautify(uglify_code, uglify_result); try_beautify(uglify_code, uglify_result, errorln);
console.log(); errorln();
console.log(); errorln();
console.log("original result:"); errorln("original result:");
console.log(original_result); errorln(typeof original_result == "string" ? original_result : original_result.stack);
console.log("uglified result:"); errorln("uglified result:");
console.log(uglify_result); errorln(typeof uglify_result == "string" ? uglify_result : uglify_result.stack);
} else { } else {
console.log("// !!! uglify failed !!!"); errorln("// !!! uglify failed !!!");
console.log(uglify_code.stack); errorln(uglify_code.stack);
if (typeof original_result != "string") { if (typeof original_result != "string") {
console.log(); errorln();
console.log(); errorln();
console.log("original stacktrace:"); errorln("original stacktrace:");
console.log(original_result.stack); errorln(original_result.stack);
} }
} }
console.log("minify(options):"); errorln("minify(options):");
options = JSON.parse(options); options = JSON.parse(options);
console.log(options); errorln(JSON.stringify(options, null, 2));
console.log(); errorln();
if (!ok && typeof uglify_code == "string") { if (!ok && typeof uglify_code == "string") {
Object.keys(default_options).forEach(log_suspects.bind(null, options)); Object.keys(default_options).forEach(log_suspects.bind(null, options));
console.log("!!!!!! Failed... round", round); errorln("!!!!!! Failed... round " + round);
} }
} }
@@ -1075,19 +1068,19 @@ for (var round = 1; round <= num_iterations; round++) {
} }
if (verbose || (verbose_interval && !(round % INTERVAL_COUNT)) || !ok) log(options); if (verbose || (verbose_interval && !(round % INTERVAL_COUNT)) || !ok) log(options);
else if (typeof original_result != "string") { else if (typeof original_result != "string") {
console.log("//============================================================="); println("//=============================================================");
console.log("// original code"); println("// original code");
try_beautify(original_code, original_result); try_beautify(original_code, original_result, println);
console.log(); println();
console.log(); println();
console.log("original result:"); println("original result:");
console.log(original_result); println(original_result.stack);
console.log(); println();
} }
if (!ok && isFinite(num_iterations)) { if (!ok && isFinite(num_iterations)) {
console.log(); println();
process.exit(1); process.exit(1);
} }
}); });
} }
console.log(); println();

View File

@@ -1,4 +1,5 @@
exports["Dictionary"] = Dictionary; exports["Dictionary"] = Dictionary;
exports["TreeWalker"] = TreeWalker; exports["TreeWalker"] = TreeWalker;
exports["TreeTransformer"] = TreeTransformer;
exports["minify"] = minify; exports["minify"] = minify;
exports["_push_uniq"] = push_uniq; exports["_push_uniq"] = push_uniq;

View File

@@ -61,5 +61,22 @@ function describe_ast() {
} }
}; };
doitem(AST_Node); doitem(AST_Node);
return out + ""; return out + "\n";
} }
function infer_options(options) {
var result = UglifyJS.minify("", options);
return result.error && result.error.defs;
}
UglifyJS.default_options = function() {
var defs = {};
Object.keys(infer_options({ 0: 0 })).forEach(function(component) {
var options = {};
options[component] = { 0: 0 };
if (options = infer_options(options)) {
defs[component] = options;
}
});
return defs;
};