Compare commits
72 Commits
harmony-v3
...
harmony-v3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
075b648bb1 | ||
|
|
37e549ff4f | ||
|
|
e95052a423 | ||
|
|
e667f0acb8 | ||
|
|
7bcb442e4c | ||
|
|
a658cd84a5 | ||
|
|
69ac794bc8 | ||
|
|
efdb65913b | ||
|
|
a1dedeb3ce | ||
|
|
5b22334f3b | ||
|
|
a3053c537a | ||
|
|
d3c4a8e9e7 | ||
|
|
d6f77a6352 | ||
|
|
7e164aba8f | ||
|
|
22aedef849 | ||
|
|
58fae7dc07 | ||
|
|
a2172e1a99 | ||
|
|
5bf8d7e949 | ||
|
|
1df9d06f4a | ||
|
|
9a074c2637 | ||
|
|
02b14528fa | ||
|
|
3408fc9d32 | ||
|
|
eae26756f1 | ||
|
|
3db2001633 | ||
|
|
aaba482e48 | ||
|
|
5f29fced0a | ||
|
|
43add9416b | ||
|
|
efcf167e5e | ||
|
|
6ed90913ca | ||
|
|
b1b918e6d6 | ||
|
|
569c21e952 | ||
|
|
87c3a2c0ce | ||
|
|
baef8bf050 | ||
|
|
0813c5316f | ||
|
|
ebb469e4cd | ||
|
|
c22d26b483 | ||
|
|
f751e64d49 | ||
|
|
60c56a24b9 | ||
|
|
c88139492d | ||
|
|
cb45886512 | ||
|
|
01f23cf5a1 | ||
|
|
99fb3e8f0d | ||
|
|
050474ab44 | ||
|
|
f6c805ae1d | ||
|
|
9464d3c20f | ||
|
|
f18abd1b9c | ||
|
|
3be06ad085 | ||
|
|
265008c948 | ||
|
|
756c9aa7dc | ||
|
|
07d6bfd707 | ||
|
|
81243c4e71 | ||
|
|
cd6e849555 | ||
|
|
ff526be61d | ||
|
|
e005099fb1 | ||
|
|
504a436e9d | ||
|
|
3ca902258c | ||
|
|
91de285166 | ||
|
|
4d8f289eb0 | ||
|
|
fd0951231c | ||
|
|
9e29b6dad2 | ||
|
|
c391576d52 | ||
|
|
ac73c5d421 | ||
|
|
547f41beba | ||
|
|
945ba64160 | ||
|
|
c699200398 | ||
|
|
daf44f2b21 | ||
|
|
daaefc17b9 | ||
|
|
1d407e761e | ||
|
|
2b44f4ae30 | ||
|
|
e51c3541da | ||
|
|
3bf194684b | ||
|
|
fcd90db30d |
@@ -1,5 +1,4 @@
|
||||
language: node_js
|
||||
before_install: "npm install -g npm"
|
||||
node_js:
|
||||
- "0.10"
|
||||
- "0.12"
|
||||
|
||||
718
README.md
718
README.md
@@ -1,6 +1,5 @@
|
||||
uglify-es
|
||||
=========
|
||||
[](https://travis-ci.org/mishoo/UglifyJS2)
|
||||
|
||||
**uglify-es** is an ECMAScript 2015 parser, minifier, compressor and beautifier toolkit.
|
||||
|
||||
@@ -22,8 +21,7 @@ From NPM for programmatic use:
|
||||
|
||||
npm install uglify-es
|
||||
|
||||
Usage
|
||||
-----
|
||||
# Command line usage
|
||||
|
||||
uglifyjs [input files] [options]
|
||||
|
||||
@@ -40,7 +38,7 @@ a double dash to prevent input files being used as option arguments:
|
||||
|
||||
uglifyjs --compress --mangle -- input.js
|
||||
|
||||
The available options are:
|
||||
### Command line options
|
||||
|
||||
```
|
||||
-h, --help Print usage information.
|
||||
@@ -126,7 +124,7 @@ The available options are:
|
||||
`url` If specified, path to the source map to append in
|
||||
`//# sourceMappingURL`.
|
||||
--stats 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.
|
||||
--warn Print warning messages.
|
||||
--wrap <name> Embed everything in a big function, making the
|
||||
@@ -139,23 +137,28 @@ The available options are:
|
||||
Specify `--output` (`-o`) to declare the output file. Otherwise the output
|
||||
goes to STDOUT.
|
||||
|
||||
## Source map options
|
||||
## CLI source map options
|
||||
|
||||
UglifyJS can generate a source map file, which is highly useful for
|
||||
debugging your compressed JavaScript. To get a source map, pass
|
||||
`--source-map --output output.js` (source map will be written out to
|
||||
`output.js.map`).
|
||||
|
||||
Additionally you might need `--source-map root=<URL>` to pass the URL where
|
||||
the original files can be found. Use `--source-map url=<URL>` to specify
|
||||
the URL where the source map can be found.
|
||||
Additional options:
|
||||
|
||||
- `--source-map filename=<NAME>` to specify the name of the source map.
|
||||
|
||||
- `--source-map root=<URL>` to pass the URL where the original files can be found.
|
||||
Otherwise UglifyJS assumes HTTP `X-SourceMap` is being used and will omit the
|
||||
`//# sourceMappingURL=` directive.
|
||||
|
||||
- `--source-map url=<URL>` to specify the URL where the source map can be found.
|
||||
|
||||
For example:
|
||||
|
||||
uglifyjs /home/doe/work/foo/src/js/file1.js \
|
||||
/home/doe/work/foo/src/js/file2.js \
|
||||
uglifyjs js/file1.js js/file2.js \
|
||||
-o foo.min.js -c -m \
|
||||
--source-map base="/home/doe/work/foo/src",root="http://foo.com/src"
|
||||
--source-map root="http://foo.com/src",url=foo.min.js.map
|
||||
|
||||
The above will compress and mangle `file1.js` and `file2.js`, will drop the
|
||||
output in `foo.min.js` and the source map in `foo.min.js.map`. The source
|
||||
@@ -174,18 +177,29 @@ CoffeeScript → compiled JS, UglifyJS can generate a map from CoffeeScript →
|
||||
compressed JS by mapping every token in the compiled JS to its original
|
||||
location.
|
||||
|
||||
To use this feature you need to pass `--in-source-map
|
||||
/path/to/input/source.map` or `--in-source-map inline` if the source map is
|
||||
included inline with the sources. Normally the input source map should also
|
||||
point to the file containing the generated JS, so if that's correct you can
|
||||
omit input files from the command line.
|
||||
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
|
||||
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
|
||||
(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).
|
||||
|
||||
- `eval` — mangle names visible in scopes where `eval` or `with` are used
|
||||
@@ -199,16 +213,16 @@ comma-separated list of names. For example:
|
||||
|
||||
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
|
||||
separate step, different from variable name mangling. Pass
|
||||
`--mangle-props`. It will mangle all properties that are seen in some
|
||||
object literal, or that are assigned to. For example:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
var x = {
|
||||
foo: 1
|
||||
foo: 1
|
||||
};
|
||||
|
||||
x.bar = 2;
|
||||
@@ -237,10 +251,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.
|
||||
It should be initially empty. Example:
|
||||
|
||||
```
|
||||
rm -f /tmp/cache.json # start fresh
|
||||
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
|
||||
```bash
|
||||
$ rm -f /tmp/cache.json # start fresh
|
||||
$ 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
|
||||
```
|
||||
|
||||
Now, `part1.js` and `part2.js` will be consistent with each other in terms
|
||||
@@ -249,18 +263,18 @@ of mangled property names.
|
||||
Using the name cache is not necessary if you compress all your files in a
|
||||
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`)
|
||||
so that it is not mangled throughout the entire script even when used in an
|
||||
unquoted style (`o.foo`). Example:
|
||||
|
||||
```
|
||||
```bash
|
||||
$ echo 'var o={"foo":1, bar:3}; o.foo += o.bar; console.log(o.foo);' | uglifyjs --mangle-props keep_quoted -mc
|
||||
var o={foo:1,a:3};o.foo+=o.a,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
|
||||
without completely obscuring them. For example the property `o.foo`
|
||||
@@ -275,12 +289,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
|
||||
identify mistakes like writing mangled keys to storage.
|
||||
|
||||
## Compressor options
|
||||
|
||||
You need to pass `--compress` (`-c`) to enable the compressor. Optionally
|
||||
you can pass a comma-separated list of options. Options are in the form
|
||||
`foo=bar`, or just `foo` (the latter implies a boolean option that you want
|
||||
to set `true`; it's effectively a shortcut for `foo=true`).
|
||||
# API Reference
|
||||
|
||||
Assuming installation via NPM, you can load UglifyJS in your application
|
||||
like this:
|
||||
```javascript
|
||||
var UglifyJS = require("uglify-es");
|
||||
```
|
||||
|
||||
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
|
||||
comma operator. May be set to a positive integer to specify the maximum number
|
||||
@@ -313,6 +526,9 @@ to set `true`; it's effectively a shortcut for `foo=true`).
|
||||
- `unsafe_proto` (default: false) -- optimize expressions like
|
||||
`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
|
||||
expressions
|
||||
|
||||
@@ -332,7 +548,7 @@ to set `true`; it's effectively a shortcut for `foo=true`).
|
||||
assignments do not count as references unless set to `"keep_assign"`)
|
||||
|
||||
- `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)
|
||||
|
||||
- `top_retain` -- prevent specific toplevel functions and variables from `unused`
|
||||
@@ -350,8 +566,8 @@ to set `true`; it's effectively a shortcut for `foo=true`).
|
||||
- `cascade` -- small optimization for sequences, transform `x, x` into `x`
|
||||
and `x = something(), x` into `x = something()`
|
||||
|
||||
- `collapse_vars` -- Collapse single-use `var` and `const` definitions
|
||||
when possible.
|
||||
- `collapse_vars` -- Collapse single-use non-constant variables - side
|
||||
effects permitting.
|
||||
|
||||
- `reduce_vars` -- Improve optimization on variables assigned with and
|
||||
used as constant values.
|
||||
@@ -396,14 +612,149 @@ to set `true`; it's effectively a shortcut for `foo=true`).
|
||||
compressor from discarding function names. Useful for code relying on
|
||||
`Function.prototype.name`. See also: the `keep_fnames` [mangle option](#mangle).
|
||||
|
||||
- `passes` -- default `1`. Number of times to run compress. Use an
|
||||
integer argument larger than 1 to further reduce code size in some cases.
|
||||
Note: raising the number of passes will increase uglify compress time.
|
||||
- `passes` -- default `1`. Number of times to run compress with a maximum of 3.
|
||||
In some cases more than one pass leads to further compressed code. Keep in
|
||||
mind more passes will take more time.
|
||||
|
||||
- `keep_infinity` -- default `false`. Pass `true` to prevent `Infinity` from
|
||||
being compressed into `1/0`, which may cause performance issues on Chrome.
|
||||
|
||||
### The `unsafe` option
|
||||
- `side_effects` -- default `true`. Pass `false` to disable potentially dropping
|
||||
functions marked as "pure". A function call is marked as "pure" if a comment
|
||||
annotation `/*@__PURE__*/` or `/*#__PURE__*/` immediately precedes the call. For
|
||||
example: `/*@__PURE__*/foo();`
|
||||
|
||||
## Mangle options
|
||||
|
||||
- `reserved` - pass an array of identifiers that should be excluded from mangling
|
||||
|
||||
- `toplevel` — mangle names declared in the top level 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](#compress-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.
|
||||
|
||||
## 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.
|
||||
- `ecma` (default `5`) -- set output printing mode. This will only change the
|
||||
output in direct control of the beautifier. Non-compatible features in the
|
||||
abstract syntax tree will still be outputted as is.
|
||||
- `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.
|
||||
- `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
|
||||
contrived cases, but should be fine for most code. You might want to try it
|
||||
@@ -427,7 +778,7 @@ scope). For example if you pass `--define DEBUG=false` then, coupled with
|
||||
dead code removal UglifyJS will discard the following from the output:
|
||||
```javascript
|
||||
if (DEBUG) {
|
||||
console.log("debug stuff");
|
||||
console.log("debug stuff");
|
||||
}
|
||||
```
|
||||
|
||||
@@ -441,8 +792,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
|
||||
`build/defines.js` file with the following:
|
||||
```javascript
|
||||
const DEBUG = false;
|
||||
const PRODUCTION = true;
|
||||
var DEBUG = false;
|
||||
var PRODUCTION = true;
|
||||
// etc.
|
||||
```
|
||||
|
||||
@@ -456,12 +807,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`,
|
||||
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
|
||||
property name is `global_defs` and is a compressor property:
|
||||
|
||||
```js
|
||||
uglifyJS.minify([ "input.js"], {
|
||||
```javascript
|
||||
var result = UglifyJS.minify(fs.readFileSync("input.js", "utf8"), {
|
||||
compress: {
|
||||
dead_code: true,
|
||||
global_defs: {
|
||||
@@ -471,84 +823,72 @@ uglifyJS.minify([ "input.js"], {
|
||||
});
|
||||
```
|
||||
|
||||
## Beautifier 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:
|
||||
|
||||
- `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.
|
||||
- `ecma` (default `5`) -- set output printing mode. This will only change the
|
||||
output in direct control of the beautifier. Non-compatible features in the
|
||||
abstract syntax tree will still be outputted as is.
|
||||
|
||||
### 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:
|
||||
To replace an identifier with an arbitrary non-constant expression it is
|
||||
necessary to prefix the `global_defs` key with `"@"` to instruct UglifyJS
|
||||
to parse the value as an expression:
|
||||
```javascript
|
||||
function f() {
|
||||
/** @preserve Foo Bar */
|
||||
function g() {
|
||||
// this function is never called
|
||||
}
|
||||
return something();
|
||||
}
|
||||
UglifyJS.minify("alert('hello');", {
|
||||
compress: {
|
||||
global_defs: {
|
||||
"@alert": "console.log"
|
||||
}
|
||||
}
|
||||
}).code;
|
||||
// returns: 'console.log("hello");'
|
||||
```
|
||||
|
||||
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.
|
||||
Otherwise it would be replaced as string literal:
|
||||
```javascript
|
||||
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
|
||||
needs to be kept in the output) are comments attached to toplevel nodes.
|
||||
### Using native Uglify AST with `minify()`
|
||||
```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
|
||||
[practical reasons](http://lisperator.net/blog/uglifyjs-why-not-switching-to-spidermonkey-ast/)
|
||||
@@ -576,139 +916,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
|
||||
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-es");
|
||||
```
|
||||
|
||||
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
|
||||
//tst.js
|
||||
var globalVar;
|
||||
function funcName(firstLongName, anotherLongName)
|
||||
{
|
||||
var myVariable = firstLongName + anotherLongName;
|
||||
}
|
||||
|
||||
UglifyJS.minify("tst.js").code;
|
||||
// 'function funcName(a,n){}var globalVar;'
|
||||
|
||||
UglifyJS.minify("tst.js", { mangle: { reserved: ['firstLongName'] } }).code;
|
||||
// 'function funcName(firstLongName,a){}var globalVar;'
|
||||
|
||||
UglifyJS.minify("tst.js", { 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
|
||||
[sm-spec]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k
|
||||
|
||||
[acorn]: https://github.com/ternjs/acorn
|
||||
[sm-spec]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k
|
||||
|
||||
@@ -21,8 +21,7 @@ var options = {
|
||||
compress: false,
|
||||
mangle: false
|
||||
};
|
||||
program._name = info.name;
|
||||
program.version(info.version);
|
||||
program.version(info.name + ' ' + info.version);
|
||||
program.parseArgv = program.parse;
|
||||
program.parse = undefined;
|
||||
program.option("-p, --parse <options>", "Specify parser options.", parse_js("parse", true));
|
||||
|
||||
@@ -932,7 +932,7 @@ var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", {
|
||||
$documentation: "Base class for literal object properties",
|
||||
$propdoc: {
|
||||
key: "[string|AST_Node] the property name converted to a string for ObjectKeyVal. For setters, getters and computed property this is an arbitrary AST_Node",
|
||||
value: "[AST_Node] property value. For setters and getters this is an AST_Function."
|
||||
value: "[AST_Node] property value. For setters and getters this is an AST_Accessor."
|
||||
},
|
||||
_walk: function(visitor) {
|
||||
return visitor._visit(this, function(){
|
||||
@@ -1018,10 +1018,6 @@ var AST_NewTarget = DEFNODE("NewTarget", null, {
|
||||
$documentation: "A reference to new.target"
|
||||
});
|
||||
|
||||
var AST_SymbolAccessor = DEFNODE("SymbolAccessor", null, {
|
||||
$documentation: "The name of a property accessor (setter/getter function)"
|
||||
}, AST_Symbol);
|
||||
|
||||
var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", "init", {
|
||||
$documentation: "A declaration symbol (symbol in var/const, function name or argument, symbol in catch)",
|
||||
}, AST_Symbol);
|
||||
|
||||
131
lib/compress.js
131
lib/compress.js
@@ -84,9 +84,21 @@ function Compressor(options, false_by_default) {
|
||||
unsafe_comps : false,
|
||||
unsafe_math : false,
|
||||
unsafe_proto : false,
|
||||
unsafe_regexp : false,
|
||||
unused : !false_by_default,
|
||||
warnings : false,
|
||||
}, 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"];
|
||||
if (typeof pure_funcs == "function") {
|
||||
this.pure_funcs = pure_funcs;
|
||||
@@ -361,16 +373,27 @@ merge(Compressor.prototype, {
|
||||
// So existing transformation rules can work on them.
|
||||
node.argnames.forEach(function(arg, i) {
|
||||
var d = arg.definition();
|
||||
d.fixed = function() {
|
||||
return iife.args[i] || make_node(AST_Undefined, iife);
|
||||
};
|
||||
mark(d, true);
|
||||
if (!node.uses_arguments && d.fixed === undefined) {
|
||||
d.fixed = function() {
|
||||
return iife.args[i] || make_node(AST_Undefined, iife);
|
||||
};
|
||||
mark(d, true);
|
||||
} else {
|
||||
d.fixed = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
descend();
|
||||
pop();
|
||||
return true;
|
||||
}
|
||||
if (node instanceof AST_Accessor) {
|
||||
var save_ids = safe_ids;
|
||||
safe_ids = Object.create(null);
|
||||
descend();
|
||||
safe_ids = save_ids;
|
||||
return true;
|
||||
}
|
||||
if (node instanceof AST_Binary
|
||||
&& (node.operator == "&&" || node.operator == "||")) {
|
||||
node.left.walk(tw);
|
||||
@@ -498,7 +521,9 @@ merge(Compressor.prototype, {
|
||||
|
||||
function reset_def(def) {
|
||||
def.escaped = false;
|
||||
if (!def.global || def.orig[0] instanceof AST_SymbolConst || compressor.toplevel(def)) {
|
||||
if (def.scope.uses_eval) {
|
||||
def.fixed = false;
|
||||
} else if (!def.global || def.orig[0] instanceof AST_SymbolConst || compressor.toplevel(def)) {
|
||||
def.fixed = undefined;
|
||||
} else {
|
||||
def.fixed = false;
|
||||
@@ -532,6 +557,14 @@ merge(Compressor.prototype, {
|
||||
return lhs instanceof AST_SymbolRef && lhs.definition().orig[0] instanceof AST_SymbolLambda;
|
||||
}
|
||||
|
||||
function is_ref_of(ref, type) {
|
||||
if (!(ref instanceof AST_SymbolRef)) return false;
|
||||
var orig = ref.definition().orig;
|
||||
for (var i = orig.length; --i >= 0;) {
|
||||
if (orig[i] instanceof type) return true;
|
||||
}
|
||||
}
|
||||
|
||||
function find_variable(compressor, name) {
|
||||
var scope, i = 0;
|
||||
while (scope = compressor.parent(i++)) {
|
||||
@@ -806,7 +839,8 @@ merge(Compressor.prototype, {
|
||||
return make_node(AST_SymbolRef, expr.name, expr.name);
|
||||
}
|
||||
} else {
|
||||
return expr[expr instanceof AST_Assign ? "left" : "expression"];
|
||||
var lhs = expr[expr instanceof AST_Assign ? "left" : "expression"];
|
||||
return !is_ref_of(lhs, AST_SymbolConst) && lhs;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -917,12 +951,12 @@ merge(Compressor.prototype, {
|
||||
continue loop;
|
||||
case stat instanceof AST_If:
|
||||
if (stat.body instanceof AST_Return) {
|
||||
var value = stat.body.value;
|
||||
//---
|
||||
// pretty silly case, but:
|
||||
// if (foo()) return; return; ==> foo(); return;
|
||||
if (((in_lambda && ret.length == 0)
|
||||
|| (ret[0] instanceof AST_Return && !ret[0].value))
|
||||
&& !stat.body.value && !stat.alternative) {
|
||||
if ((in_lambda && ret.length == 0 || ret[0] instanceof AST_Return && !ret[0].value)
|
||||
&& !value && !stat.alternative) {
|
||||
CHANGED = true;
|
||||
var cond = make_node(AST_SimpleStatement, stat.condition, {
|
||||
body: stat.condition
|
||||
@@ -932,7 +966,7 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
//---
|
||||
// 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;
|
||||
stat = stat.clone();
|
||||
stat.alternative = ret[0];
|
||||
@@ -942,7 +976,7 @@ merge(Compressor.prototype, {
|
||||
//---
|
||||
// if (foo()) return x; [ return ; ] ==> return foo() ? x : undefined;
|
||||
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;
|
||||
stat = stat.clone();
|
||||
stat.alternative = ret[0] || make_node(AST_Return, stat, {
|
||||
@@ -952,8 +986,8 @@ merge(Compressor.prototype, {
|
||||
continue loop;
|
||||
}
|
||||
//---
|
||||
// if (foo()) return; [ else x... ]; y... ==> if (!foo()) { x...; y... }
|
||||
if (!stat.body.value && in_lambda) {
|
||||
// if (foo()) return [ void bar() ]; [ else x...; ] y... ==> if (!foo()) { x...; y... } else bar();
|
||||
if (in_lambda && (!value || value instanceof AST_UnaryPrefix && value.operator == "void")) {
|
||||
CHANGED = true;
|
||||
stat = stat.clone();
|
||||
stat.condition = stat.condition.negate(compressor);
|
||||
@@ -962,11 +996,12 @@ merge(Compressor.prototype, {
|
||||
stat.body = make_node(AST_BlockStatement, stat, {
|
||||
body: body
|
||||
});
|
||||
stat.alternative = null;
|
||||
stat.alternative = value ? make_node(AST_SimpleStatement, value, {
|
||||
body: value.expression
|
||||
}) : 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;
|
||||
//
|
||||
@@ -1231,12 +1266,12 @@ merge(Compressor.prototype, {
|
||||
&& !node.expression.has_side_effects(compressor);
|
||||
}
|
||||
|
||||
// may_eq_null()
|
||||
// returns true if this node may evaluate to null or undefined
|
||||
// may_throw_on_access()
|
||||
// returns true if this node may be null, undefined or contain `AST_Accessor`
|
||||
(function(def) {
|
||||
AST_Node.DEFMETHOD("may_eq_null", function(compressor) {
|
||||
AST_Node.DEFMETHOD("may_throw_on_access", function(compressor) {
|
||||
var pure_getters = compressor.option("pure_getters");
|
||||
return !pure_getters || this._eq_null(pure_getters);
|
||||
return !pure_getters || this._throw_on_access(pure_getters);
|
||||
});
|
||||
|
||||
function is_strict(pure_getters) {
|
||||
@@ -1248,7 +1283,12 @@ merge(Compressor.prototype, {
|
||||
def(AST_Undefined, return_true);
|
||||
def(AST_Constant, return_false);
|
||||
def(AST_Array, return_false);
|
||||
def(AST_Object, return_false);
|
||||
def(AST_Object, function(pure_getters) {
|
||||
if (!is_strict(pure_getters)) return false;
|
||||
for (var i = this.properties.length; --i >=0;)
|
||||
if (this.properties[i].value instanceof AST_Accessor) return true;
|
||||
return false;
|
||||
});
|
||||
def(AST_Function, return_false);
|
||||
def(AST_UnaryPostfix, return_false);
|
||||
def(AST_UnaryPrefix, function() {
|
||||
@@ -1257,33 +1297,33 @@ merge(Compressor.prototype, {
|
||||
def(AST_Binary, function(pure_getters) {
|
||||
switch (this.operator) {
|
||||
case "&&":
|
||||
return this.left._eq_null(pure_getters);
|
||||
return this.left._throw_on_access(pure_getters);
|
||||
case "||":
|
||||
return this.left._eq_null(pure_getters)
|
||||
&& this.right._eq_null(pure_getters);
|
||||
return this.left._throw_on_access(pure_getters)
|
||||
&& this.right._throw_on_access(pure_getters);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
})
|
||||
def(AST_Assign, function(pure_getters) {
|
||||
return this.operator == "="
|
||||
&& this.right._eq_null(pure_getters);
|
||||
&& this.right._throw_on_access(pure_getters);
|
||||
})
|
||||
def(AST_Conditional, function(pure_getters) {
|
||||
return this.consequent._eq_null(pure_getters)
|
||||
|| this.alternative._eq_null(pure_getters);
|
||||
return this.consequent._throw_on_access(pure_getters)
|
||||
|| this.alternative._throw_on_access(pure_getters);
|
||||
})
|
||||
def(AST_Sequence, function(pure_getters) {
|
||||
return this.expressions[this.expressions.length - 1]._eq_null(pure_getters);
|
||||
return this.expressions[this.expressions.length - 1]._throw_on_access(pure_getters);
|
||||
});
|
||||
def(AST_SymbolRef, function(pure_getters) {
|
||||
if (this.is_undefined) return true;
|
||||
if (!is_strict(pure_getters)) return false;
|
||||
var fixed = this.fixed_value();
|
||||
return !fixed || fixed._eq_null(pure_getters);
|
||||
return !fixed || fixed._throw_on_access(pure_getters);
|
||||
});
|
||||
})(function(node, func) {
|
||||
node.DEFMETHOD("_eq_null", func);
|
||||
node.DEFMETHOD("_throw_on_access", func);
|
||||
});
|
||||
|
||||
/* -----[ boolean/negation helpers ]----- */
|
||||
@@ -1844,11 +1884,11 @@ merge(Compressor.prototype, {
|
||||
return any(this.elements, compressor);
|
||||
});
|
||||
def(AST_Dot, function(compressor){
|
||||
return this.expression.may_eq_null(compressor)
|
||||
return this.expression.may_throw_on_access(compressor)
|
||||
|| this.expression.has_side_effects(compressor);
|
||||
});
|
||||
def(AST_Sub, function(compressor){
|
||||
return this.expression.may_eq_null(compressor)
|
||||
return this.expression.may_throw_on_access(compressor)
|
||||
|| this.expression.has_side_effects(compressor)
|
||||
|| this.property.has_side_effects(compressor);
|
||||
});
|
||||
@@ -2029,6 +2069,7 @@ merge(Compressor.prototype, {
|
||||
&& node instanceof AST_Assign
|
||||
&& node.operator == "="
|
||||
&& node.left instanceof AST_SymbolRef
|
||||
&& !is_ref_of(node.left, AST_SymbolBlockDeclaration)
|
||||
&& scope === self) {
|
||||
node.right.walk(tw);
|
||||
return true;
|
||||
@@ -2067,15 +2108,21 @@ merge(Compressor.prototype, {
|
||||
}));
|
||||
}
|
||||
}
|
||||
else if (node.names[i] instanceof AST_Hole) {
|
||||
continue;
|
||||
}
|
||||
else if (node.names[i] instanceof AST_ObjectKeyVal && typeof node.names[i].key === "string") {
|
||||
initializations.add(node.names[i].key, destructuring_value);
|
||||
else if (node.names[i] instanceof AST_ObjectKeyVal) {
|
||||
if (typeof node.names[i].key === "string") {
|
||||
initializations.add(node.names[i].key, destructuring_value);
|
||||
}
|
||||
}
|
||||
else if (node.names[i] instanceof AST_Symbol) {
|
||||
initializations.add(node.names[i].name, destructuring_value);
|
||||
} else {
|
||||
}
|
||||
else if (node.names[i] instanceof AST_DefaultAssign) {
|
||||
continue;
|
||||
}
|
||||
else if (node.names[i] instanceof AST_Hole) {
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
throw new Error(string_template("Unknown destructuring element of type: {type}", {
|
||||
type: Object.getPrototypeOf(node.names[i]).TYPE
|
||||
}));
|
||||
@@ -2482,6 +2529,7 @@ merge(Compressor.prototype, {
|
||||
var args = trim(this.args, compressor, first_in_statement);
|
||||
return args && make_sequence(this, args);
|
||||
});
|
||||
def(AST_Accessor, return_null);
|
||||
def(AST_Function, return_null);
|
||||
def(AST_Binary, function(compressor, first_in_statement){
|
||||
var right = this.right.drop_side_effect_free(compressor);
|
||||
@@ -2549,11 +2597,11 @@ merge(Compressor.prototype, {
|
||||
return values && make_sequence(this, values);
|
||||
});
|
||||
def(AST_Dot, function(compressor, first_in_statement){
|
||||
if (this.expression.may_eq_null(compressor)) return this;
|
||||
if (this.expression.may_throw_on_access(compressor)) return this;
|
||||
return this.expression.drop_side_effect_free(compressor, first_in_statement);
|
||||
});
|
||||
def(AST_Sub, function(compressor, first_in_statement){
|
||||
if (this.expression.may_eq_null(compressor)) return this;
|
||||
if (this.expression.may_throw_on_access(compressor)) return this;
|
||||
var expression = this.expression.drop_side_effect_free(compressor, first_in_statement);
|
||||
if (!expression) return this.property.drop_side_effect_free(compressor, first_in_statement);
|
||||
var property = this.property.drop_side_effect_free(compressor);
|
||||
@@ -3131,7 +3179,6 @@ merge(Compressor.prototype, {
|
||||
// Symbol's argument is only used for debugging.
|
||||
self.args = [];
|
||||
return self;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (exp instanceof AST_Dot && exp.property == "toString" && self.args.length == 0) {
|
||||
@@ -3314,7 +3361,7 @@ merge(Compressor.prototype, {
|
||||
&& (left.operator == "++" || left.operator == "--")) {
|
||||
left = left.expression;
|
||||
} else left = null;
|
||||
if (!left || is_lhs_read_only(left)) {
|
||||
if (!left || is_lhs_read_only(left) || is_ref_of(left, AST_SymbolConst)) {
|
||||
expressions[++i] = cdr;
|
||||
continue;
|
||||
}
|
||||
@@ -3883,7 +3930,7 @@ merge(Compressor.prototype, {
|
||||
if (fixed) {
|
||||
if (d.should_replace === undefined) {
|
||||
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);
|
||||
var value_length = init.optimize(compressor).print_to_string().length;
|
||||
var fn;
|
||||
|
||||
@@ -54,9 +54,11 @@ function minify(files, options) {
|
||||
cache: null,
|
||||
eval: false,
|
||||
ie8: false,
|
||||
keep_classnames: false,
|
||||
keep_fnames: false,
|
||||
properties: false,
|
||||
reserved: [],
|
||||
safari10: false,
|
||||
toplevel: false,
|
||||
}, true);
|
||||
}
|
||||
|
||||
@@ -111,23 +111,19 @@
|
||||
},
|
||||
Property: function(M) {
|
||||
var key = M.key;
|
||||
var name = key.type == "Identifier" ? key.name : key.value;
|
||||
var args = {
|
||||
start : my_start_token(key),
|
||||
end : my_end_token(M.value),
|
||||
key : name,
|
||||
key : key.type == "Identifier" ? key.name : key.value,
|
||||
value : from_moz(M.value)
|
||||
};
|
||||
switch (M.kind) {
|
||||
case "init":
|
||||
return new AST_ObjectKeyVal(args);
|
||||
case "set":
|
||||
args.value.name = from_moz(key);
|
||||
return new AST_ObjectSetter(args);
|
||||
case "get":
|
||||
args.value.name = from_moz(key);
|
||||
return new AST_ObjectGetter(args);
|
||||
}
|
||||
if (M.kind == "init") return new AST_ObjectKeyVal(args);
|
||||
args.key = new AST_SymbolMethod({
|
||||
name: args.key
|
||||
});
|
||||
args.value = new AST_Accessor(args.value);
|
||||
if (M.kind == "get") return new AST_ObjectGetter(args);
|
||||
if (M.kind == "set") return new AST_ObjectSetter(args);
|
||||
},
|
||||
ArrayExpression: function(M) {
|
||||
return new AST_Array({
|
||||
@@ -260,10 +256,7 @@
|
||||
map("CallExpression", AST_Call, "callee>expression, arguments@args");
|
||||
|
||||
def_to_moz(AST_Toplevel, function To_Moz_Program(M) {
|
||||
return {
|
||||
type: "Program",
|
||||
body: M.body.map(to_moz)
|
||||
};
|
||||
return to_moz_scope("Program", M);
|
||||
});
|
||||
|
||||
def_to_moz(AST_Defun, function To_Moz_FunctionDeclaration(M) {
|
||||
@@ -271,7 +264,7 @@
|
||||
type: "FunctionDeclaration",
|
||||
id: to_moz(M.name),
|
||||
params: M.argnames.map(to_moz),
|
||||
body: to_moz_block(M)
|
||||
body: to_moz_scope("BlockStatement", M)
|
||||
}
|
||||
});
|
||||
|
||||
@@ -280,7 +273,7 @@
|
||||
type: "FunctionExpression",
|
||||
id: to_moz(M.name),
|
||||
params: M.argnames.map(to_moz),
|
||||
body: to_moz_block(M)
|
||||
body: to_moz_scope("BlockStatement", M)
|
||||
}
|
||||
});
|
||||
|
||||
@@ -386,11 +379,10 @@
|
||||
});
|
||||
|
||||
def_to_moz(AST_ObjectProperty, function To_Moz_Property(M) {
|
||||
var key = (
|
||||
is_identifier(M.key)
|
||||
? {type: "Identifier", name: M.key}
|
||||
: {type: "Literal", value: M.key}
|
||||
);
|
||||
var key = {
|
||||
type: "Literal",
|
||||
value: M.key instanceof AST_SymbolMethod ? M.key.name : M.key
|
||||
};
|
||||
var kind;
|
||||
if (M instanceof AST_ObjectKeyVal) {
|
||||
kind = "init";
|
||||
@@ -551,8 +543,8 @@
|
||||
moz_to_me = new Function("U2", "my_start_token", "my_end_token", "from_moz", "return(" + moz_to_me + ")")(
|
||||
exports, my_start_token, my_end_token, from_moz
|
||||
);
|
||||
me_to_moz = new Function("to_moz", "to_moz_block", "return(" + me_to_moz + ")")(
|
||||
to_moz, to_moz_block
|
||||
me_to_moz = new Function("to_moz", "to_moz_block", "to_moz_scope", "return(" + me_to_moz + ")")(
|
||||
to_moz, to_moz_block, to_moz_scope
|
||||
);
|
||||
MOZ_TO_ME[moztype] = moz_to_me;
|
||||
def_to_moz(mytype, me_to_moz);
|
||||
@@ -610,4 +602,14 @@
|
||||
};
|
||||
};
|
||||
|
||||
function to_moz_scope(type, node) {
|
||||
var body = node.body.map(to_moz);
|
||||
if (node.body[0] instanceof AST_SimpleStatement && node.body[0].body instanceof AST_String) {
|
||||
body.unshift(to_moz(new AST_EmptyStatement(node.body[0])));
|
||||
}
|
||||
return {
|
||||
type: type,
|
||||
body: body
|
||||
};
|
||||
};
|
||||
})();
|
||||
|
||||
133
lib/output.js
133
lib/output.js
@@ -73,8 +73,6 @@ function OutputStream(options) {
|
||||
shebang : true,
|
||||
shorthand : undefined,
|
||||
source_map : null,
|
||||
space_colon : true,
|
||||
unescape_regexps : false,
|
||||
width : 80,
|
||||
wrap_iife : false,
|
||||
}, true);
|
||||
@@ -214,12 +212,43 @@ function OutputStream(options) {
|
||||
var might_need_semicolon = false;
|
||||
var might_add_newline = 0;
|
||||
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() {
|
||||
if (current_col > options.max_line_len) {
|
||||
if (might_add_newline) {
|
||||
var left = OUTPUT.slice(0, 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;
|
||||
current_line++;
|
||||
current_pos++;
|
||||
@@ -229,7 +258,10 @@ function OutputStream(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;
|
||||
|
||||
var requireSemicolonChars = makePredicate("( [ + * / - , .");
|
||||
@@ -289,6 +321,18 @@ function OutputStream(options) {
|
||||
}
|
||||
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;
|
||||
current_pos += str.length;
|
||||
var a = str.split(/\r?\n/), n = a.length - 1;
|
||||
@@ -384,27 +428,12 @@ function OutputStream(options) {
|
||||
|
||||
function colon() {
|
||||
print(":");
|
||||
if (options.space_colon) space();
|
||||
space();
|
||||
};
|
||||
|
||||
var add_mapping = options.source_map ? function(token, name) {
|
||||
try {
|
||||
if (token) options.source_map.add(
|
||||
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 || ""
|
||||
})
|
||||
}
|
||||
var add_mapping = mappings ? function(token, name) {
|
||||
mapping_token = token;
|
||||
mapping_name = name;
|
||||
} : noop;
|
||||
|
||||
function get() {
|
||||
@@ -644,7 +673,6 @@ function OutputStream(options) {
|
||||
* ==> 20 (side effect, set a := 10 and b := 20) */
|
||||
|| p instanceof AST_Arrow // x => (x, x)
|
||||
|| p instanceof AST_DefaultAssign // x => (x = (0, function(){}))
|
||||
|| (p instanceof AST_Class && p.extends === this) // class D extends (calls++, C) {}
|
||||
;
|
||||
});
|
||||
|
||||
@@ -1474,10 +1502,24 @@ function OutputStream(options) {
|
||||
output.space();
|
||||
}
|
||||
if (self.extends) {
|
||||
var parens = (
|
||||
!(self.extends instanceof AST_SymbolRef)
|
||||
&& !(self.extends instanceof AST_PropAccess)
|
||||
&& !(self.extends instanceof AST_ClassExpression)
|
||||
&& !(self.extends instanceof AST_Function)
|
||||
);
|
||||
output.print("extends");
|
||||
output.space();
|
||||
if (parens) {
|
||||
output.print("(");
|
||||
} else {
|
||||
output.space();
|
||||
}
|
||||
self.extends.print(output);
|
||||
output.space();
|
||||
if (parens) {
|
||||
output.print(")");
|
||||
} else {
|
||||
output.space();
|
||||
}
|
||||
}
|
||||
if (self.properties.length > 0) output.with_block(function(){
|
||||
self.properties.forEach(function(prop, i){
|
||||
@@ -1617,45 +1659,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){
|
||||
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")) {
|
||||
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);
|
||||
var p = output.parent();
|
||||
|
||||
171
lib/parse.js
171
lib/parse.js
@@ -345,7 +345,11 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
|
||||
(type == "keyword" && KEYWORDS_BEFORE_EXPRESSION(value)) ||
|
||||
(type == "punc" && PUNC_BEFORE_EXPRESSION(value))) ||
|
||||
(type == "arrow");
|
||||
prev_was_dot = (type == "punc" && value == ".");
|
||||
if (type == "punc" && value == ".") {
|
||||
prev_was_dot = true;
|
||||
} else if (!is_comment) {
|
||||
prev_was_dot = false;
|
||||
}
|
||||
var ret = {
|
||||
type : type,
|
||||
value : value,
|
||||
@@ -607,31 +611,33 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
|
||||
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;
|
||||
while ((ch = next(true))) if (NEWLINE_CHARS(ch)) {
|
||||
parse_error("Unexpected line terminator");
|
||||
} else if (prev_backslash) {
|
||||
regexp += "\\" + ch;
|
||||
source += "\\" + ch;
|
||||
prev_backslash = false;
|
||||
} else if (ch == "[") {
|
||||
in_class = true;
|
||||
regexp += ch;
|
||||
source += ch;
|
||||
} else if (ch == "]" && in_class) {
|
||||
in_class = false;
|
||||
regexp += ch;
|
||||
source += ch;
|
||||
} else if (ch == "/" && !in_class) {
|
||||
break;
|
||||
} else if (ch == "\\") {
|
||||
prev_backslash = true;
|
||||
} else {
|
||||
regexp += ch;
|
||||
source += ch;
|
||||
}
|
||||
var mods = read_name();
|
||||
try {
|
||||
return token("regexp", new RegExp(regexp, mods));
|
||||
var regexp = new RegExp(source, mods);
|
||||
regexp.raw_source = source;
|
||||
return token("regexp", regexp);
|
||||
} catch(e) {
|
||||
parse_error(e.message);
|
||||
parse_error(e.message);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -972,28 +978,23 @@ function parse($TEXT, options) {
|
||||
};
|
||||
|
||||
var statement = embed_tokens(function() {
|
||||
var tmp;
|
||||
handle_regexp();
|
||||
switch (S.token.type) {
|
||||
case "string":
|
||||
var dir = false;
|
||||
if (S.in_directives === true) {
|
||||
if ((is_token(peek(), "punc", ";") || peek().nlb) && S.token.raw.indexOf("\\") === -1) {
|
||||
if (S.in_directives) {
|
||||
var token = peek();
|
||||
if (S.token.raw.indexOf("\\") == -1
|
||||
&& (token.nlb
|
||||
|| is_token(token, "eof")
|
||||
|| is_token(token, "punc", ";")
|
||||
|| is_token(token, "punc", "}"))) {
|
||||
S.input.add_directive(S.token.value);
|
||||
} else {
|
||||
S.in_directives = false;
|
||||
}
|
||||
}
|
||||
var dir = S.in_directives, stat = simple_statement();
|
||||
if (dir) {
|
||||
return new AST_Directive({
|
||||
start : stat.body.start,
|
||||
end : stat.body.end,
|
||||
quote : stat.body.quote,
|
||||
value : stat.body.value,
|
||||
});
|
||||
}
|
||||
return stat;
|
||||
return dir ? new AST_Directive(stat.body) : stat;
|
||||
case "template_head":
|
||||
case "num":
|
||||
case "regexp":
|
||||
@@ -1026,90 +1027,126 @@ function parse($TEXT, options) {
|
||||
}
|
||||
|
||||
case "keyword":
|
||||
switch (tmp = S.token.value, next(), tmp) {
|
||||
switch (S.token.value) {
|
||||
case "break":
|
||||
next();
|
||||
return break_cont(AST_Break);
|
||||
|
||||
case "continue":
|
||||
next();
|
||||
return break_cont(AST_Continue);
|
||||
|
||||
case "debugger":
|
||||
next();
|
||||
semicolon();
|
||||
return new AST_Debugger();
|
||||
|
||||
case "do":
|
||||
next();
|
||||
var body = in_loop(statement);
|
||||
expect_token("keyword", "while");
|
||||
var condition = parenthesised();
|
||||
semicolon(true);
|
||||
return new AST_Do({
|
||||
body : in_loop(statement),
|
||||
condition : (expect_token("keyword", "while"), tmp = parenthesised(), semicolon(true), tmp)
|
||||
body : body,
|
||||
condition : condition
|
||||
});
|
||||
|
||||
case "while":
|
||||
next();
|
||||
return new AST_While({
|
||||
condition : parenthesised(),
|
||||
body : in_loop(statement)
|
||||
});
|
||||
|
||||
case "for":
|
||||
next();
|
||||
return for_();
|
||||
|
||||
case "class":
|
||||
next();
|
||||
return class_(AST_DefClass);
|
||||
|
||||
case "function":
|
||||
next();
|
||||
return function_(AST_Defun);
|
||||
|
||||
case "if":
|
||||
next();
|
||||
return if_();
|
||||
|
||||
case "return":
|
||||
if (S.in_function == 0 && !options.bare_returns)
|
||||
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({
|
||||
value: ( is("punc", ";")
|
||||
? (next(), null)
|
||||
: can_insert_semicolon()
|
||||
? null
|
||||
: (tmp = expression(true), semicolon(), tmp) )
|
||||
value: value
|
||||
});
|
||||
|
||||
case "switch":
|
||||
next();
|
||||
return new AST_Switch({
|
||||
expression : parenthesised(),
|
||||
body : in_loop(switch_body_)
|
||||
});
|
||||
|
||||
case "throw":
|
||||
next();
|
||||
if (S.token.nlb)
|
||||
croak("Illegal newline after 'throw'");
|
||||
var value = expression(true);
|
||||
semicolon();
|
||||
return new AST_Throw({
|
||||
value: (tmp = expression(true), semicolon(), tmp)
|
||||
value: value
|
||||
});
|
||||
|
||||
case "try":
|
||||
next();
|
||||
return try_();
|
||||
|
||||
case "var":
|
||||
return tmp = var_(), semicolon(), tmp;
|
||||
next();
|
||||
var node = var_();
|
||||
semicolon();
|
||||
return node;
|
||||
|
||||
case "let":
|
||||
return tmp = let_(), semicolon(), tmp;
|
||||
next();
|
||||
var node = let_();
|
||||
semicolon();
|
||||
return node;
|
||||
|
||||
case "const":
|
||||
return tmp = const_(), semicolon(), tmp;
|
||||
next();
|
||||
var node = const_();
|
||||
semicolon();
|
||||
return node;
|
||||
|
||||
case "with":
|
||||
if (S.input.has_directive("use strict")) {
|
||||
croak("Strict mode may not include a with statement");
|
||||
}
|
||||
next();
|
||||
return new AST_With({
|
||||
expression : parenthesised(),
|
||||
body : statement()
|
||||
});
|
||||
|
||||
case "import":
|
||||
return tmp = import_(), semicolon(), tmp;
|
||||
next();
|
||||
var node = import_();
|
||||
semicolon();
|
||||
return node;
|
||||
|
||||
case "export":
|
||||
next();
|
||||
return export_();
|
||||
}
|
||||
}
|
||||
@@ -2302,46 +2339,43 @@ function parse($TEXT, options) {
|
||||
if (is("keyword", "default")) {
|
||||
is_default = true;
|
||||
next();
|
||||
}
|
||||
} else {
|
||||
exported_names = import_names(false);
|
||||
|
||||
exported_names = import_names(false);
|
||||
if (exported_names) {
|
||||
if (is("name", "from")) {
|
||||
next();
|
||||
|
||||
if (exported_names) {
|
||||
if (is("name", "from")) {
|
||||
next();
|
||||
var mod_str = S.token;
|
||||
if (mod_str.type !== 'string') {
|
||||
unexpected();
|
||||
}
|
||||
next();
|
||||
|
||||
var mod_str = S.token;
|
||||
if (mod_str.type !== 'string') {
|
||||
unexpected();
|
||||
return new AST_Export({
|
||||
start: start,
|
||||
is_default: is_default,
|
||||
exported_names: exported_names,
|
||||
module_name: new AST_String({
|
||||
start: mod_str,
|
||||
value: mod_str.value,
|
||||
quote: mod_str.quote,
|
||||
end: mod_str,
|
||||
}),
|
||||
end: prev(),
|
||||
});
|
||||
} else {
|
||||
return new AST_Export({
|
||||
start: start,
|
||||
is_default: is_default,
|
||||
exported_names: exported_names,
|
||||
end: prev(),
|
||||
});
|
||||
}
|
||||
next();
|
||||
|
||||
return new AST_Export({
|
||||
start: start,
|
||||
is_default: is_default,
|
||||
exported_names: exported_names,
|
||||
module_name: new AST_String({
|
||||
start: mod_str,
|
||||
value: mod_str.value,
|
||||
quote: mod_str.quote,
|
||||
end: mod_str,
|
||||
}),
|
||||
end: prev(),
|
||||
});
|
||||
} else {
|
||||
return new AST_Export({
|
||||
start: start,
|
||||
is_default: is_default,
|
||||
exported_names: exported_names,
|
||||
end: prev(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var is_definition =
|
||||
is("keyword", "var") || is("keyword", "let") || is("keyword", "const") ||
|
||||
is("keyword", "class") || is("keyword", "function");
|
||||
|
||||
var is_definition = is("keyword", "var") || is("keyword", "let") || is("keyword", "const");
|
||||
if (is_definition) {
|
||||
exported_definition = statement();
|
||||
} else {
|
||||
@@ -2377,7 +2411,8 @@ function parse($TEXT, options) {
|
||||
unexpected(tmp);
|
||||
}
|
||||
case "name":
|
||||
if (tmp.value === "yield" && S.input.has_directive("use strict") && !is_in_generator()) {
|
||||
if (tmp.value == "yield" && !is_token(peek(), "punc", ":")
|
||||
&& S.input.has_directive("use strict") && !is_in_generator()) {
|
||||
token_error(tmp, "Unexpected yield identifier inside strict mode");
|
||||
}
|
||||
case "string":
|
||||
|
||||
@@ -48,9 +48,10 @@ function find_builtins(reserved) {
|
||||
// Compatibility fix for some standard defined globals not defined on every js environment
|
||||
var new_globals = ["Symbol", "Map", "Promise", "Proxy", "Reflect", "Set", "WeakMap", "WeakSet"];
|
||||
var objects = {};
|
||||
var global_ref = typeof global === "object" ? global : self;
|
||||
|
||||
new_globals.forEach(function (new_global) {
|
||||
objects[new_global] = global[new_global] || new Function();
|
||||
objects[new_global] = global_ref[new_global] || new Function();
|
||||
});
|
||||
|
||||
// NaN will be included due to Number.NaN
|
||||
|
||||
26
lib/scope.js
26
lib/scope.js
@@ -102,6 +102,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
|
||||
options = defaults(options, {
|
||||
cache: null,
|
||||
ie8: false,
|
||||
safari10: false,
|
||||
});
|
||||
|
||||
// pass 1: setup scope chaining and handle definitions
|
||||
@@ -112,6 +113,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
|
||||
var in_destructuring = null;
|
||||
var in_export = false;
|
||||
var in_block = 0;
|
||||
var for_scopes = [];
|
||||
var tw = new TreeWalker(function(node, descend){
|
||||
if (node.is_block_scope()) {
|
||||
var save_scope = scope;
|
||||
@@ -122,6 +124,11 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
|
||||
scope.uses_eval = save_scope.uses_eval;
|
||||
scope.directives = save_scope.directives;
|
||||
}
|
||||
if (options.safari10) {
|
||||
if (node instanceof AST_For || node instanceof AST_ForIn) {
|
||||
for_scopes.push(scope);
|
||||
}
|
||||
}
|
||||
descend();
|
||||
scope = save_scope;
|
||||
return true;
|
||||
@@ -303,6 +310,19 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
|
||||
}));
|
||||
}
|
||||
|
||||
// pass 4: add symbol definitions to loop scopes
|
||||
// Safari/Webkit bug workaround - loop init let variable shadowing argument.
|
||||
// https://github.com/mishoo/UglifyJS2/issues/1753
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=171041
|
||||
if (options.safari10) {
|
||||
for (var i = 0; i < for_scopes.length; i++) {
|
||||
var scope = for_scopes[i];
|
||||
scope.parent_scope.variables.each(function(def) {
|
||||
push_uniq(scope.enclosed, def);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (options.cache) {
|
||||
this.cname = options.cache.cname;
|
||||
}
|
||||
@@ -453,11 +473,6 @@ AST_Symbol.DEFMETHOD("unmangleable", function(options){
|
||||
return def && def.unmangleable(options);
|
||||
});
|
||||
|
||||
// property accessors are not mangleable
|
||||
AST_SymbolAccessor.DEFMETHOD("unmangleable", function(){
|
||||
return true;
|
||||
});
|
||||
|
||||
// labels are always mangleable
|
||||
AST_Label.DEFMETHOD("unmangleable", function(){
|
||||
return false;
|
||||
@@ -554,7 +569,6 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
|
||||
});
|
||||
this.walk(tw);
|
||||
to_mangle.forEach(function(def){
|
||||
if (def.destructuring && !def.destructuring.is_array) return;
|
||||
def.mangle(options);
|
||||
});
|
||||
|
||||
|
||||
@@ -239,6 +239,10 @@ TreeTransformer.prototype = new TreeWalker;
|
||||
self.expression = self.expression.transform(tw);
|
||||
});
|
||||
|
||||
_(AST_Export, function(self, tw){
|
||||
if (self.exported_value) self.exported_value = self.exported_value.transform(tw);
|
||||
});
|
||||
|
||||
_(AST_TemplateString, function(self, tw) {
|
||||
for (var i = 0; i < self.segments.length; i++) {
|
||||
if (!(self.segments[i] instanceof AST_TemplateSegment)) {
|
||||
|
||||
22
package.json
22
package.json
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "uglify-es",
|
||||
"description": "JavaScript parser, mangler/compressor and beautifier toolkit",
|
||||
"homepage": "http://lisperator.net/uglifyjs",
|
||||
"homepage": "https://github.com/mishoo/UglifyJS2/tree/harmony",
|
||||
"author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
|
||||
"license": "BSD-2-Clause",
|
||||
"version": "3.0.3",
|
||||
"version": "3.0.11",
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
},
|
||||
@@ -33,22 +33,12 @@
|
||||
"source-map": "~0.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"acorn": "~0.6.0",
|
||||
"escodegen": "~1.3.3",
|
||||
"esfuzz": "~0.3.1",
|
||||
"estraverse": "~1.5.1",
|
||||
"mocha": "~2.3.4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"uglify-to-browserify": "~1.0.0"
|
||||
},
|
||||
"browserify": {
|
||||
"transform": [
|
||||
"uglify-to-browserify"
|
||||
]
|
||||
"acorn": "~5.0.3",
|
||||
"mocha": "~2.3.4",
|
||||
"semver": "~5.3.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node test/run-tests.js"
|
||||
},
|
||||
"keywords": ["uglify", "uglify-js", "minify", "minifier"]
|
||||
"keywords": ["uglify", "uglify-js", "uglify-es", "minify", "minifier", "es5", "es6", "es2015"]
|
||||
}
|
||||
|
||||
@@ -2284,3 +2284,49 @@ compound_assignment: {
|
||||
}
|
||||
expect_stdout: "4"
|
||||
}
|
||||
|
||||
reassign_const_1: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
}
|
||||
input: {
|
||||
function f() {
|
||||
const a = 1;
|
||||
a = 2;
|
||||
return a;
|
||||
}
|
||||
console.log(f());
|
||||
}
|
||||
expect: {
|
||||
function f() {
|
||||
const a = 1;
|
||||
a = 2;
|
||||
return a;
|
||||
}
|
||||
console.log(f());
|
||||
}
|
||||
expect_stdout: true
|
||||
}
|
||||
|
||||
reassign_const_2: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
}
|
||||
input: {
|
||||
function f() {
|
||||
const a = 1;
|
||||
++a;
|
||||
return a;
|
||||
}
|
||||
console.log(f());
|
||||
}
|
||||
expect: {
|
||||
function f() {
|
||||
const a = 1;
|
||||
++a;
|
||||
return a;
|
||||
}
|
||||
console.log(f());
|
||||
}
|
||||
expect_stdout: true
|
||||
}
|
||||
|
||||
@@ -278,3 +278,19 @@ try_catch_finally: {
|
||||
"1",
|
||||
]
|
||||
}
|
||||
|
||||
accessor: {
|
||||
options = {
|
||||
side_effects: true,
|
||||
}
|
||||
input: {
|
||||
({
|
||||
get a() {},
|
||||
set a(v){
|
||||
this.b = 2;
|
||||
},
|
||||
b: 1
|
||||
});
|
||||
}
|
||||
expect: {}
|
||||
}
|
||||
|
||||
@@ -328,5 +328,218 @@ issue_1886: {
|
||||
let [a] = [1];
|
||||
console.log(a);
|
||||
}
|
||||
expect_exact: "1"
|
||||
}
|
||||
|
||||
destructuring_decl_of_numeric_key: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
let { 3: x } = { [1 + 2]: 42 };
|
||||
console.log(x);
|
||||
}
|
||||
expect: {
|
||||
let { 3: x } = { [3]: 42 };
|
||||
console.log(x);
|
||||
}
|
||||
expect_stdout: "42"
|
||||
node_version: ">=6"
|
||||
}
|
||||
|
||||
destructuring_decl_of_computed_key: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
let four = 4;
|
||||
let { [7 - four]: x } = { [1 + 2]: 42 };
|
||||
console.log(x);
|
||||
}
|
||||
expect: {
|
||||
let four = 4;
|
||||
let { [7 - four]: x } = { [3]: 42 };
|
||||
console.log(x);
|
||||
}
|
||||
expect_stdout: "42"
|
||||
node_version: ">=6"
|
||||
}
|
||||
|
||||
destructuring_assign_of_numeric_key: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
let x;
|
||||
({ 3: x } = { [1 + 2]: 42 });
|
||||
console.log(x);
|
||||
}
|
||||
expect: {
|
||||
let x;
|
||||
({ 3: x } = { [3]: 42 });
|
||||
console.log(x);
|
||||
}
|
||||
expect_stdout: "42"
|
||||
node_version: ">=6"
|
||||
}
|
||||
|
||||
destructuring_assign_of_computed_key: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
let x;
|
||||
let four = 4;
|
||||
({ [(5 + 2) - four]: x } = { [1 + 2]: 42 });
|
||||
console.log(x);
|
||||
}
|
||||
expect: {
|
||||
let x;
|
||||
let four = 4;
|
||||
({ [7 - four]: x } = { [3]: 42 });
|
||||
console.log(x);
|
||||
}
|
||||
expect_stdout: "42"
|
||||
node_version: ">=6"
|
||||
}
|
||||
|
||||
mangle_destructuring_decl: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
unused: true,
|
||||
}
|
||||
mangle = {
|
||||
}
|
||||
input: {
|
||||
function test(opts) {
|
||||
let a = opts.a || { e: 7, n: 8 };
|
||||
let { t, e, n, s = 5 + 4, o, r } = a;
|
||||
console.log(t, e, n, s, o, r);
|
||||
}
|
||||
test({a: { t: 1, e: 2, n: 3, s: 4, o: 5, r: 6 }});
|
||||
test({});
|
||||
}
|
||||
expect: {
|
||||
function test(t) {
|
||||
let e = t.a || { e: 7, n: 8 };
|
||||
let {t: n, e: o, n: s, s: a = 9, o: c, r: l} = e;
|
||||
console.log(n, o, s, a, c, l);
|
||||
}
|
||||
test({ a: { t: 1, e: 2, n: 3, s: 4, o: 5, r: 6 } });
|
||||
test({});
|
||||
}
|
||||
expect_stdout: [
|
||||
"1 2 3 4 5 6",
|
||||
"undefined 7 8 9 undefined undefined",
|
||||
]
|
||||
node_version: ">=6"
|
||||
}
|
||||
|
||||
mangle_destructuring_assign_toplevel_true: {
|
||||
options = {
|
||||
toplevel: true,
|
||||
evaluate: true,
|
||||
unused: true,
|
||||
}
|
||||
mangle = {
|
||||
toplevel: true,
|
||||
}
|
||||
beautify = {
|
||||
ecma: 6
|
||||
}
|
||||
input: {
|
||||
function test(opts) {
|
||||
let s, o, r;
|
||||
let a = opts.a || { e: 7, n: 8 };
|
||||
({ t, e, n, s = 5 + 4, o, r } = a);
|
||||
console.log(t, e, n, s, o, r);
|
||||
}
|
||||
let t, e, n;
|
||||
test({a: { t: 1, e: 2, n: 3, s: 4, o: 5, r: 6 }});
|
||||
test({});
|
||||
}
|
||||
expect: {
|
||||
function n(n) {
|
||||
let t, a, c;
|
||||
let l = n.a || { e: 7, n: 8 };
|
||||
({t: o, e, n: s, s: t = 9, o: a, r: c} = l);
|
||||
console.log(o, e, s, t, a, c);
|
||||
}
|
||||
let o, e, s;
|
||||
n({ a: { t: 1, e: 2, n: 3, s: 4, o: 5, r: 6 } });
|
||||
n({});
|
||||
}
|
||||
expect_stdout: [
|
||||
"1 2 3 4 5 6",
|
||||
"undefined 7 8 9 undefined undefined",
|
||||
]
|
||||
node_version: ">=6"
|
||||
}
|
||||
|
||||
mangle_destructuring_assign_toplevel_false: {
|
||||
options = {
|
||||
toplevel: false,
|
||||
evaluate: true,
|
||||
unused: true,
|
||||
}
|
||||
mangle = {
|
||||
toplevel: false,
|
||||
}
|
||||
beautify = {
|
||||
ecma: 6
|
||||
}
|
||||
input: {
|
||||
function test(opts) {
|
||||
let s, o, r;
|
||||
let a = opts.a || { e: 7, n: 8 };
|
||||
({ t, e, n, s = 9, o, r } = a);
|
||||
console.log(t, e, n, s, o, r);
|
||||
}
|
||||
let t, e, n;
|
||||
test({a: { t: 1, e: 2, n: 3, s: 4, o: 5, r: 6 }});
|
||||
test({});
|
||||
}
|
||||
expect: {
|
||||
function test(o) {
|
||||
let s, a, c;
|
||||
let l = o.a || { e: 7, n: 8 };
|
||||
({t, e, n, s = 9, o: a, r: c} = l);
|
||||
console.log(t, e, n, s, a, c);
|
||||
}
|
||||
let t, e, n;
|
||||
test({ a: { t: 1, e: 2, n: 3, s: 4, o: 5, r: 6 } });
|
||||
test({});
|
||||
}
|
||||
expect_stdout: [
|
||||
"1 2 3 4 5 6",
|
||||
"undefined 7 8 9 undefined undefined",
|
||||
]
|
||||
node_version: ">=6"
|
||||
}
|
||||
|
||||
mangle_destructuring_decl_array: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
unused: true,
|
||||
toplevel: true,
|
||||
}
|
||||
mangle = {
|
||||
toplevel: true,
|
||||
}
|
||||
beautify = {
|
||||
ecma: 6
|
||||
}
|
||||
input: {
|
||||
var [, t, e, n, s, o = 2, r = [ 1 + 2 ]] = [ 9, 8, 7, 6 ];
|
||||
console.log(t, e, n, s, o, r);
|
||||
}
|
||||
expect: {
|
||||
var [, o, l, a, c, e = 2, g = [ 3 ]] = [ 9, 8, 7, 6 ];
|
||||
console.log(o, l, a, c, e, g);
|
||||
}
|
||||
expect_stdout: "8 7 6 undefined 2 [ 3 ]"
|
||||
node_version: ">=6"
|
||||
}
|
||||
|
||||
@@ -1228,3 +1228,55 @@ var_catch_toplevel: {
|
||||
}();
|
||||
}
|
||||
}
|
||||
|
||||
reassign_const: {
|
||||
options = {
|
||||
cascade: true,
|
||||
sequences: true,
|
||||
side_effects: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
function f() {
|
||||
const a = 1;
|
||||
a = 2;
|
||||
return a;
|
||||
}
|
||||
console.log(f());
|
||||
}
|
||||
expect: {
|
||||
function f() {
|
||||
const a = 1;
|
||||
return a = 2, a;
|
||||
}
|
||||
console.log(f());
|
||||
}
|
||||
expect_stdout: true
|
||||
}
|
||||
|
||||
issue_1968: {
|
||||
options = {
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
function f(c) {
|
||||
var a;
|
||||
if (c) {
|
||||
let b;
|
||||
return (a = 2) + (b = 3);
|
||||
}
|
||||
}
|
||||
console.log(f(1));
|
||||
}
|
||||
expect: {
|
||||
function f(c) {
|
||||
if (c) {
|
||||
let b;
|
||||
return 2 + (b = 3);
|
||||
}
|
||||
}
|
||||
console.log(f(1));
|
||||
}
|
||||
expect_stdout: "5"
|
||||
node_version: ">=6"
|
||||
}
|
||||
|
||||
@@ -1083,3 +1083,50 @@ 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"
|
||||
}
|
||||
|
||||
@@ -160,3 +160,17 @@ issue_1801: {
|
||||
console.log(!0);
|
||||
}
|
||||
}
|
||||
|
||||
issue_1986: {
|
||||
options = {
|
||||
global_defs: {
|
||||
"@alert": "console.log",
|
||||
},
|
||||
}
|
||||
input: {
|
||||
alert(42);
|
||||
}
|
||||
expect: {
|
||||
console.log(42);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,15 +203,66 @@ import_all_statement: {
|
||||
}
|
||||
|
||||
export_statement: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
}
|
||||
input: {
|
||||
export default 1;
|
||||
export default 1 + 2;
|
||||
export var foo = 4;
|
||||
export let foo = 6;
|
||||
export const foo = 6;
|
||||
export function foo() {};
|
||||
export class foo { };
|
||||
}
|
||||
expect_exact: "export default 1;export var foo=4;export let foo=6;export const foo=6;export function foo(){};export class foo{};"
|
||||
expect_exact: "export default 3;export var foo=4;export let foo=6;export const foo=6;export function foo(){};export class foo{};"
|
||||
}
|
||||
|
||||
export_default_object_expression: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
}
|
||||
input: {
|
||||
export default {
|
||||
foo: 1 + 2,
|
||||
bar() { return 4; },
|
||||
get baz() { return this.foo; },
|
||||
};
|
||||
}
|
||||
expect_exact: "export default{foo:3,bar(){return 4},get baz(){return this.foo}};"
|
||||
}
|
||||
|
||||
export_default_array: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
}
|
||||
input: {
|
||||
export default [ 1 + 2, foo ];
|
||||
}
|
||||
expect_exact: "export default[3,foo];"
|
||||
}
|
||||
|
||||
export_default_anon_function: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
}
|
||||
input: {
|
||||
export default function(){
|
||||
console.log(1 + 2);
|
||||
}
|
||||
}
|
||||
expect_exact: "export default function(){console.log(3)};"
|
||||
}
|
||||
|
||||
export_default_anon_class: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
}
|
||||
input: {
|
||||
export default class {
|
||||
foo() { console.log(1 + 2); }
|
||||
}
|
||||
}
|
||||
expect_exact: "export default class{foo(){console.log(3)}};"
|
||||
}
|
||||
|
||||
export_module_statement: {
|
||||
@@ -424,3 +475,111 @@ issue_1898: {
|
||||
new Foo().bar();
|
||||
}
|
||||
}
|
||||
|
||||
issue_1753: {
|
||||
mangle = { safari10: true };
|
||||
input: {
|
||||
class SomeClass {
|
||||
constructor(props) {
|
||||
let pickedSets = [];
|
||||
for (let i = 0; i < 6; i++) {
|
||||
pickedSets.push({
|
||||
mainDrawNumbers: [],
|
||||
extraDrawNumbers: []
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
class SomeClass {
|
||||
constructor(r) {
|
||||
let a = [];
|
||||
for (let s = 0; s < 6; s++)
|
||||
a.push({
|
||||
mainDrawNumbers: [],
|
||||
extraDrawNumbers: []
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
issue_1753_disable: {
|
||||
mangle = { safari10: false }
|
||||
input: {
|
||||
class SomeClass {
|
||||
constructor(props) {
|
||||
let pickedSets = [];
|
||||
for (let i = 0; i < 6; i++) {
|
||||
pickedSets.push({
|
||||
mainDrawNumbers: [],
|
||||
extraDrawNumbers: []
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
class SomeClass {
|
||||
constructor(r) {
|
||||
let a = [];
|
||||
for (let r = 0; r < 6; r++)
|
||||
a.push({
|
||||
mainDrawNumbers: [],
|
||||
extraDrawNumbers: []
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class_extends: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
}
|
||||
input: {
|
||||
function f() {
|
||||
class foo extends bar {}
|
||||
class pro extends some.prop {}
|
||||
class arr extends stuff[1 - 1] {}
|
||||
class bin extends (a || b) {}
|
||||
class seq extends (a, b) {}
|
||||
class ter extends (a ? b : c) {}
|
||||
class uni extends (!0) {}
|
||||
}
|
||||
}
|
||||
expect_exact: "function f(){class foo extends bar{}class pro extends some.prop{}class arr extends stuff[0]{}class bin extends(a||b){}class seq extends(a,b){}class ter extends(a?b:c){}class uni extends(!0){}}"
|
||||
}
|
||||
|
||||
class_extends_class: {
|
||||
options = {
|
||||
}
|
||||
input: {
|
||||
class anon extends class {} {}
|
||||
class named extends class base {} {}
|
||||
}
|
||||
expect_exact: "class anon extends class{}{}class named extends class base{}{}"
|
||||
}
|
||||
|
||||
class_extends_function: {
|
||||
options = {
|
||||
}
|
||||
input: {
|
||||
class anon extends function(){} {}
|
||||
class named extends function base(){} {}
|
||||
}
|
||||
expect_exact: "class anon extends function(){}{}class named extends function base(){}{}"
|
||||
}
|
||||
|
||||
class_extends_regex: {
|
||||
options = {
|
||||
}
|
||||
input: {
|
||||
function f() {
|
||||
class rx1 extends (/rx/) {}
|
||||
// class rx2 extends /rx/ {} // FIXME - parse error
|
||||
}
|
||||
}
|
||||
expect_exact: "function f(){class rx1 extends(/rx/){}}"
|
||||
}
|
||||
|
||||
@@ -302,3 +302,25 @@ issue_1437_conditionals: {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
issue_512: {
|
||||
options = {
|
||||
conditionals: true,
|
||||
if_return: true,
|
||||
}
|
||||
input: {
|
||||
function a() {
|
||||
if (b()) {
|
||||
c();
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
function a() {
|
||||
if (!b()) throw e;
|
||||
c();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
31
test/compress/issue-1943.js
Normal file
31
test/compress/issue-1943.js
Normal 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;"
|
||||
}
|
||||
12
test/compress/node_version.js
Normal file
12
test/compress/node_version.js
Normal file
@@ -0,0 +1,12 @@
|
||||
eval_let: {
|
||||
input: {
|
||||
eval("let a;");
|
||||
console.log();
|
||||
}
|
||||
expect: {
|
||||
eval("let a;");
|
||||
console.log();
|
||||
}
|
||||
expect_stdout: ""
|
||||
node_version: ">=6"
|
||||
}
|
||||
@@ -119,3 +119,62 @@ chained: {
|
||||
a.b.c;
|
||||
}
|
||||
}
|
||||
|
||||
impure_getter_1: {
|
||||
options = {
|
||||
pure_getters: "strict",
|
||||
side_effects: true,
|
||||
}
|
||||
input: {
|
||||
({
|
||||
get a() {
|
||||
console.log(1);
|
||||
},
|
||||
b: 1
|
||||
}).a;
|
||||
({
|
||||
get a() {
|
||||
console.log(1);
|
||||
},
|
||||
b: 1
|
||||
}).b;
|
||||
}
|
||||
expect: {
|
||||
({
|
||||
get a() {
|
||||
console.log(1);
|
||||
},
|
||||
b: 1
|
||||
}).a;
|
||||
({
|
||||
get a() {
|
||||
console.log(1);
|
||||
},
|
||||
b: 1
|
||||
}).b;
|
||||
}
|
||||
expect_stdout: "1"
|
||||
}
|
||||
|
||||
impure_getter_2: {
|
||||
options = {
|
||||
pure_getters: true,
|
||||
side_effects: true,
|
||||
}
|
||||
input: {
|
||||
// will produce incorrect output because getter is not pure
|
||||
({
|
||||
get a() {
|
||||
console.log(1);
|
||||
},
|
||||
b: 1
|
||||
}).a;
|
||||
({
|
||||
get a() {
|
||||
console.log(1);
|
||||
},
|
||||
b: 1
|
||||
}).b;
|
||||
}
|
||||
expect: {}
|
||||
}
|
||||
|
||||
@@ -41,20 +41,20 @@ reduce_vars: {
|
||||
var A = 1;
|
||||
(function() {
|
||||
console.log(-3);
|
||||
console.log(-4);
|
||||
console.log(A - 5);
|
||||
})();
|
||||
(function f1() {
|
||||
var a = 2;
|
||||
console.log(-3);
|
||||
console.log(a - 5);
|
||||
eval("console.log(a);");
|
||||
})();
|
||||
(function f2(eval) {
|
||||
var a = 2;
|
||||
console.log(-3);
|
||||
console.log(a - 5);
|
||||
eval("console.log(a);");
|
||||
})(eval);
|
||||
"yes";
|
||||
console.log(2);
|
||||
console.log(A + 1);
|
||||
}
|
||||
expect_stdout: true
|
||||
}
|
||||
@@ -1749,7 +1749,10 @@ redefine_arguments_3: {
|
||||
console.log(function() {
|
||||
var arguments;
|
||||
return typeof arguments;
|
||||
}(), "number", "undefined");
|
||||
}(), "number", function(x) {
|
||||
var arguments = x;
|
||||
return typeof arguments;
|
||||
}());
|
||||
}
|
||||
expect_stdout: "object number undefined"
|
||||
}
|
||||
@@ -2461,3 +2464,76 @@ issue_1865: {
|
||||
}
|
||||
expect_stdout: true
|
||||
}
|
||||
|
||||
issue_1922_1: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
reduce_vars: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
console.log(function(a) {
|
||||
arguments[0] = 2;
|
||||
return a;
|
||||
}(1));
|
||||
}
|
||||
expect: {
|
||||
console.log(function(a) {
|
||||
arguments[0] = 2;
|
||||
return a;
|
||||
}(1));
|
||||
}
|
||||
expect_stdout: "2"
|
||||
}
|
||||
|
||||
issue_1922_2: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
reduce_vars: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
console.log(function() {
|
||||
var a;
|
||||
eval("a = 1");
|
||||
return a;
|
||||
}(1));
|
||||
}
|
||||
expect: {
|
||||
console.log(function() {
|
||||
var a;
|
||||
eval("a = 1");
|
||||
return a;
|
||||
}(1));
|
||||
}
|
||||
expect_stdout: "1"
|
||||
}
|
||||
|
||||
accessor: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
reduce_vars: true,
|
||||
toplevel: true,
|
||||
}
|
||||
input: {
|
||||
var a = 1;
|
||||
console.log({
|
||||
get a() {
|
||||
a = 2;
|
||||
return a;
|
||||
},
|
||||
b: 1
|
||||
}.b, a);
|
||||
}
|
||||
expect: {
|
||||
var a = 1;
|
||||
console.log({
|
||||
get a() {
|
||||
a = 2;
|
||||
return a;
|
||||
},
|
||||
b: 1
|
||||
}.b, a);
|
||||
}
|
||||
expect_stdout: "1 1"
|
||||
}
|
||||
|
||||
@@ -710,3 +710,27 @@ issue_27: {
|
||||
})(jQuery);
|
||||
}
|
||||
}
|
||||
|
||||
reassign_const: {
|
||||
options = {
|
||||
cascade: true,
|
||||
sequences: true,
|
||||
side_effects: true,
|
||||
}
|
||||
input: {
|
||||
function f() {
|
||||
const a = 1;
|
||||
a++;
|
||||
return a;
|
||||
}
|
||||
console.log(f());
|
||||
}
|
||||
expect: {
|
||||
function f() {
|
||||
const a = 1;
|
||||
return a++, a;
|
||||
}
|
||||
console.log(f());
|
||||
}
|
||||
expect_stdout: true
|
||||
}
|
||||
|
||||
@@ -8,3 +8,12 @@ octal_escape_sequence: {
|
||||
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(/[\\\\/]/)}"
|
||||
}
|
||||
|
||||
@@ -190,3 +190,12 @@ yield_sub: {
|
||||
}
|
||||
expect_exact: 'function*foo(){yield x["foo"];(yield x)["foo"];yield(yield obj.foo())["bar"]()}'
|
||||
}
|
||||
|
||||
yield_as_ES5_property: {
|
||||
input: {
|
||||
"use strict";
|
||||
console.log({yield: 42}.yield);
|
||||
}
|
||||
expect_exact: '"use strict";console.log({yield:42}.yield);'
|
||||
expect_stdout: "42"
|
||||
}
|
||||
|
||||
1
test/input/invalid/else.js
Normal file
1
test/input/invalid/else.js
Normal file
@@ -0,0 +1 @@
|
||||
if (0) else 1;
|
||||
1
test/input/invalid/return.js
Normal file
1
test/input/invalid/return.js
Normal file
@@ -0,0 +1 @@
|
||||
return 42;
|
||||
5
test/input/issue-505/input.js
Normal file
5
test/input/issue-505/input.js
Normal file
@@ -0,0 +1,5 @@
|
||||
function test(callback) {
|
||||
'aaaaaaaaaaaaaaaa';
|
||||
callback(err, data);
|
||||
callback(err, data);
|
||||
}
|
||||
5
test/input/issue-505/output.js
Normal file
5
test/input/issue-505/output.js
Normal file
@@ -0,0 +1,5 @@
|
||||
function test(a){
|
||||
"aaaaaaaaaaaaaaaa"
|
||||
;a(err,data),a(err,data)
|
||||
}
|
||||
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjAiXSwibmFtZXMiOlsidGVzdCIsImNhbGxiYWNrIiwiZXJyIiwiZGF0YSJdLCJtYXBwaW5ncyI6IkFBQUEsU0FBU0EsS0FBS0M7QUFDVjtDQUNBQSxFQUFTQyxJQUFLQyxNQUNkRixFQUFTQyxJQUFLQyJ9
|
||||
@@ -1,2 +1,2 @@
|
||||
new function(){console.log(3)};
|
||||
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0ZGluIl0sIm5hbWVzIjpbImNvbnNvbGUiLCJsb2ciXSwibWFwcGluZ3MiOiJBQUErQyxHQUFyQyxZQUFnQkEsUUFBUUMsSUFBSSIsInNvdXJjZXNDb250ZW50IjpbImNsYXNzIEZvbyB7IGNvbnN0cnVjdG9yKCl7Y29uc29sZS5sb2coMSsyKTt9IH0gbmV3IEZvbygpO1xuIl19
|
||||
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0ZGluIl0sIm5hbWVzIjpbImNvbnNvbGUiLCJsb2ciXSwibWFwcGluZ3MiOiJBQUErQyxJQUFyQyxXQUFnQkEsUUFBUUMsSUFBSSIsInNvdXJjZXNDb250ZW50IjpbImNsYXNzIEZvbyB7IGNvbnN0cnVjdG9yKCl7Y29uc29sZS5sb2coMSsyKTt9IH0gbmV3IEZvbygpO1xuIl19
|
||||
|
||||
@@ -19,7 +19,9 @@ describe("bin/uglifyjs", function () {
|
||||
eval(stdout);
|
||||
|
||||
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();
|
||||
});
|
||||
@@ -55,15 +57,15 @@ describe("bin/uglifyjs", function () {
|
||||
});
|
||||
});
|
||||
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) {
|
||||
if (err) throw err;
|
||||
exec(command, function (err, stdout) {
|
||||
if (err) throw err;
|
||||
|
||||
assert.strictEqual(stdout, "var bar=function(){function foo(bar){return bar}return foo}();\n" +
|
||||
"//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInRlc3QvaW5wdXQvaXNzdWUtMTMyMy9zYW1wbGUuanMiXSwibmFtZXMiOlsiYmFyIiwiZm9vIl0sIm1hcHBpbmdzIjoiQUFBQSxHQUFJQSxLQUFNLFdBQ04sUUFBU0MsS0FBS0QsS0FDVixNQUFPQSxLQUdYLE1BQU9DIn0=\n");
|
||||
done();
|
||||
});
|
||||
assert.strictEqual(stdout, "var bar=function(){function foo(bar){return bar}return foo}();\n" +
|
||||
"//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInRlc3QvaW5wdXQvaXNzdWUtMTMyMy9zYW1wbGUuanMiXSwibmFtZXMiOlsiYmFyIiwiZm9vIl0sIm1hcHBpbmdzIjoiQUFBQSxJQUFJQSxJQUFNLFdBQ04sU0FBU0MsSUFBS0QsS0FDVixPQUFPQSxJQUdYLE9BQU9DIn0=\n");
|
||||
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';
|
||||
@@ -76,84 +78,84 @@ describe("bin/uglifyjs", function () {
|
||||
});
|
||||
});
|
||||
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) {
|
||||
if (err) throw err;
|
||||
exec(command, function (err, stdout) {
|
||||
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");
|
||||
done();
|
||||
});
|
||||
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();
|
||||
});
|
||||
});
|
||||
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) {
|
||||
if (err) throw err;
|
||||
exec(command, function (err, stdout) {
|
||||
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");
|
||||
done();
|
||||
});
|
||||
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();
|
||||
});
|
||||
});
|
||||
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) {
|
||||
if (err) throw err;
|
||||
exec(command, function (err, stdout) {
|
||||
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");
|
||||
done();
|
||||
});
|
||||
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();
|
||||
});
|
||||
});
|
||||
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) {
|
||||
if (err) throw err;
|
||||
exec(command, function (err, stdout) {
|
||||
if (err) throw err;
|
||||
|
||||
assert.strictEqual(stdout, "console.log(5);\n");
|
||||
done();
|
||||
});
|
||||
assert.strictEqual(stdout, "console.log(5);\n");
|
||||
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) {
|
||||
if (err) throw err;
|
||||
exec(command, function (err, stdout) {
|
||||
if (err) throw err;
|
||||
|
||||
assert.strictEqual(stdout, "console.log(3,5);\n");
|
||||
done();
|
||||
});
|
||||
assert.strictEqual(stdout, "console.log(3,5);\n");
|
||||
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) {
|
||||
if (err) throw err;
|
||||
exec(command, function (err, stdout) {
|
||||
if (err) throw err;
|
||||
|
||||
assert.strictEqual(stdout, "stdout.println(D);\n");
|
||||
done();
|
||||
});
|
||||
assert.strictEqual(stdout, "stdout.println(D);\n");
|
||||
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) {
|
||||
if (err) throw err;
|
||||
exec(command, function (err, stdout) {
|
||||
if (err) throw err;
|
||||
|
||||
assert.strictEqual(stdout, read("test/input/issue-1482/default.js"));
|
||||
done();
|
||||
});
|
||||
assert.strictEqual(stdout, read("test/input/issue-1482/default.js"));
|
||||
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) {
|
||||
if (err) throw err;
|
||||
exec(command, function (err, stdout) {
|
||||
if (err) throw err;
|
||||
|
||||
assert.strictEqual(stdout, read("test/input/issue-1482/bracketize.js"));
|
||||
done();
|
||||
});
|
||||
assert.strictEqual(stdout, read("test/input/issue-1482/bracketize.js"));
|
||||
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";
|
||||
@@ -173,7 +175,7 @@ describe("bin/uglifyjs", function () {
|
||||
|
||||
assert.strictEqual(stdout, [
|
||||
"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"));
|
||||
assert.strictEqual(stderr, "WARN: inline source map not found\n");
|
||||
@@ -260,229 +262,259 @@ describe("bin/uglifyjs", function () {
|
||||
});
|
||||
});
|
||||
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) {
|
||||
assert.ok(err);
|
||||
assert.strictEqual(stdout, "");
|
||||
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
|
||||
"Parse error at test/input/invalid/assign_1.js:1,18",
|
||||
"console.log(1 || 5--);",
|
||||
" ^",
|
||||
"ERROR: Invalid use of -- operator"
|
||||
].join("\n"));
|
||||
done();
|
||||
});
|
||||
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/assign_1.js:1,18",
|
||||
"console.log(1 || 5--);",
|
||||
" ^",
|
||||
"ERROR: Invalid use of -- operator"
|
||||
].join("\n"));
|
||||
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) {
|
||||
assert.ok(err);
|
||||
assert.strictEqual(stdout, "");
|
||||
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
|
||||
"Parse error at test/input/invalid/assign_2.js:1,32",
|
||||
"console.log(2 || (Math.random() /= 2));",
|
||||
" ^",
|
||||
"ERROR: Invalid assignment"
|
||||
].join("\n"));
|
||||
done();
|
||||
});
|
||||
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/assign_2.js:1,32",
|
||||
"console.log(2 || (Math.random() /= 2));",
|
||||
" ^",
|
||||
"ERROR: Invalid assignment"
|
||||
].join("\n"));
|
||||
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) {
|
||||
assert.ok(err);
|
||||
assert.strictEqual(stdout, "");
|
||||
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
|
||||
"Parse error at test/input/invalid/assign_3.js:1,17",
|
||||
"console.log(3 || ++this);",
|
||||
" ^",
|
||||
"ERROR: Invalid use of ++ operator"
|
||||
].join("\n"));
|
||||
done();
|
||||
});
|
||||
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/assign_3.js:1,17",
|
||||
"console.log(3 || ++this);",
|
||||
" ^",
|
||||
"ERROR: Invalid use of ++ operator"
|
||||
].join("\n"));
|
||||
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) {
|
||||
assert.ok(err);
|
||||
assert.strictEqual(stdout, "");
|
||||
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
|
||||
"Parse error at test/input/invalid/assign_4.js:1,0",
|
||||
"++null",
|
||||
"^",
|
||||
"ERROR: Invalid use of ++ operator"
|
||||
].join("\n"));
|
||||
done();
|
||||
});
|
||||
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/assign_4.js:1,0",
|
||||
"++null",
|
||||
"^",
|
||||
"ERROR: Invalid use of ++ operator"
|
||||
].join("\n"));
|
||||
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) {
|
||||
assert.ok(err);
|
||||
assert.strictEqual(stdout, "");
|
||||
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
|
||||
"Parse error at test/input/invalid/dot_1.js:1,2",
|
||||
"a.=",
|
||||
" ^",
|
||||
"ERROR: Unexpected token: operator (=)"
|
||||
].join("\n"));
|
||||
done();
|
||||
});
|
||||
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/dot_1.js:1,2",
|
||||
"a.=",
|
||||
" ^",
|
||||
"ERROR: Unexpected token: operator (=)"
|
||||
].join("\n"));
|
||||
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) {
|
||||
assert.ok(err);
|
||||
assert.strictEqual(stdout, "");
|
||||
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
|
||||
"Parse error at test/input/invalid/dot_2.js:1,0",
|
||||
"%.a;",
|
||||
"^",
|
||||
"ERROR: Unexpected token: operator (%)"
|
||||
].join("\n"));
|
||||
done();
|
||||
});
|
||||
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/dot_2.js:1,0",
|
||||
"%.a;",
|
||||
"^",
|
||||
"ERROR: Unexpected token: operator (%)"
|
||||
].join("\n"));
|
||||
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) {
|
||||
assert.ok(err);
|
||||
assert.strictEqual(stdout, "");
|
||||
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
|
||||
"Parse error at test/input/invalid/dot_3.js:1,2",
|
||||
"a./();",
|
||||
" ^",
|
||||
"ERROR: Unexpected token: operator (/)"
|
||||
].join("\n"));
|
||||
done();
|
||||
});
|
||||
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/dot_3.js:1,2",
|
||||
"a./();",
|
||||
" ^",
|
||||
"ERROR: Unexpected token: operator (/)"
|
||||
].join("\n"));
|
||||
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) {
|
||||
assert.ok(err);
|
||||
assert.strictEqual(stdout, "");
|
||||
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
|
||||
"Parse error at test/input/invalid/object.js:1,13",
|
||||
"console.log({%: 1});",
|
||||
" ^",
|
||||
"ERROR: Unexpected token: operator (%)"
|
||||
].join("\n"));
|
||||
done();
|
||||
});
|
||||
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/object.js:1,13",
|
||||
"console.log({%: 1});",
|
||||
" ^",
|
||||
"ERROR: Unexpected token: operator (%)"
|
||||
].join("\n"));
|
||||
done();
|
||||
});
|
||||
});
|
||||
it("Should throw syntax error (const a)", function(done) {
|
||||
var command = uglifyjscmd + ' test/input/invalid/const.js';
|
||||
var command = uglifyjscmd + ' test/input/invalid/const.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/const.js:7,11",
|
||||
" const a;",
|
||||
" ^",
|
||||
"ERROR: Missing initializer in const declaration"
|
||||
].join("\n"));
|
||||
done();
|
||||
});
|
||||
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/const.js:7,11",
|
||||
" const a;",
|
||||
" ^",
|
||||
"ERROR: Missing initializer in const declaration"
|
||||
].join("\n"));
|
||||
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) {
|
||||
assert.ok(err);
|
||||
assert.strictEqual(stdout, "");
|
||||
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
|
||||
"Parse error at test/input/invalid/delete.js:13,11",
|
||||
" delete x;",
|
||||
" ^",
|
||||
"ERROR: Calling delete on expression not allowed in strict mode"
|
||||
].join("\n"));
|
||||
done();
|
||||
});
|
||||
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/delete.js:13,11",
|
||||
" delete x;",
|
||||
" ^",
|
||||
"ERROR: Calling delete on expression not allowed in strict mode"
|
||||
].join("\n"));
|
||||
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) {
|
||||
assert.ok(err);
|
||||
assert.strictEqual(stdout, "");
|
||||
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
|
||||
"Parse error at test/input/invalid/function_1.js:4,11",
|
||||
"function g(arguments) {",
|
||||
" ^",
|
||||
"ERROR: Unexpected arguments in strict mode"
|
||||
].join("\n"));
|
||||
done();
|
||||
});
|
||||
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/function_1.js:4,11",
|
||||
"function g(arguments) {",
|
||||
" ^",
|
||||
"ERROR: Unexpected arguments in strict mode"
|
||||
].join("\n"));
|
||||
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) {
|
||||
assert.ok(err);
|
||||
assert.strictEqual(stdout, "");
|
||||
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
|
||||
"Parse error at test/input/invalid/function_2.js:4,9",
|
||||
"function eval() {",
|
||||
" ^",
|
||||
"ERROR: Unexpected eval in strict mode"
|
||||
].join("\n"));
|
||||
done();
|
||||
});
|
||||
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/function_2.js:4,9",
|
||||
"function eval() {",
|
||||
" ^",
|
||||
"ERROR: Unexpected eval in strict mode"
|
||||
].join("\n"));
|
||||
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) {
|
||||
assert.ok(err);
|
||||
assert.strictEqual(stdout, "");
|
||||
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
|
||||
"Parse error at test/input/invalid/function_3.js:4,10",
|
||||
"!function arguments() {",
|
||||
" ^",
|
||||
"ERROR: Unexpected arguments in strict mode"
|
||||
].join("\n"));
|
||||
done();
|
||||
});
|
||||
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/function_3.js:4,10",
|
||||
"!function arguments() {",
|
||||
" ^",
|
||||
"ERROR: Unexpected arguments in strict mode"
|
||||
].join("\n"));
|
||||
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) {
|
||||
assert.ok(err);
|
||||
assert.strictEqual(stdout, "");
|
||||
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
|
||||
"Parse error at test/input/invalid/try.js:7,18",
|
||||
" try {} catch (eval) {}",
|
||||
" ^",
|
||||
"ERROR: Unexpected eval identifier as parameter inside strict mode"
|
||||
].join("\n"));
|
||||
done();
|
||||
});
|
||||
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/try.js:7,18",
|
||||
" try {} catch (eval) {}",
|
||||
" ^",
|
||||
"ERROR: Unexpected eval identifier as parameter inside strict mode"
|
||||
].join("\n"));
|
||||
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) {
|
||||
assert.ok(err);
|
||||
assert.strictEqual(stdout, "");
|
||||
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
|
||||
"Parse error at test/input/invalid/var.js:7,8",
|
||||
" var eval;",
|
||||
" ^",
|
||||
"ERROR: Unexpected eval in strict mode"
|
||||
].join("\n"));
|
||||
done();
|
||||
});
|
||||
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/var.js:7,8",
|
||||
" var eval;",
|
||||
" ^",
|
||||
"ERROR: Unexpected eval in strict mode"
|
||||
].join("\n"));
|
||||
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) {
|
||||
var command = [
|
||||
@@ -497,7 +529,7 @@ describe("bin/uglifyjs", function () {
|
||||
|
||||
assert.strictEqual(stdout, [
|
||||
'"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"));
|
||||
done();
|
||||
|
||||
@@ -361,18 +361,28 @@ describe("Directives", function() {
|
||||
var tests = [
|
||||
[
|
||||
'"use strict";"use strict";"use strict";"use foo";"use strict";;"use sloppy";doSomething("foo");',
|
||||
'"use strict";"use foo";doSomething("foo");'
|
||||
'"use strict";"use foo";doSomething("foo");',
|
||||
'function f(){ "use strict" }',
|
||||
'function f(){ "use asm" }',
|
||||
'function f(){ "use nondirective" }',
|
||||
'function f(){ ;"use strict" }',
|
||||
'function f(){ "use \n"; }',
|
||||
],
|
||||
[
|
||||
// Nothing gets optimised in the compressor because "use asm" is the first statement
|
||||
'"use asm";"use\\x20strict";1+1;',
|
||||
'"use asm";;"use strict";1+1;' // Yet, the parser noticed that "use strict" wasn't a directive
|
||||
'"use asm";;"use strict";1+1;', // Yet, the parser noticed that "use strict" wasn't a directive
|
||||
'function f(){"use strict"}',
|
||||
'function f(){"use asm"}',
|
||||
'function f(){"use nondirective"}',
|
||||
'function f(){}',
|
||||
'function f(){}',
|
||||
]
|
||||
];
|
||||
|
||||
for (var i = 0; i < tests.length; i++) {
|
||||
assert.strictEqual(
|
||||
uglify.minify(tests[i][0], {compress: {collapse_vars: true, side_effects: true}}).code,
|
||||
uglify.minify(tests[i][0]).code,
|
||||
tests[i][1],
|
||||
tests[i][0]
|
||||
);
|
||||
|
||||
@@ -138,13 +138,25 @@ describe("minify", function() {
|
||||
});
|
||||
var code = result.code;
|
||||
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() {
|
||||
var result = Uglify.minify('var a = function(foo) { return foo; };');
|
||||
var code = result.code;
|
||||
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() {
|
||||
@@ -181,4 +193,19 @@ describe("minify", function() {
|
||||
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");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,7 +15,9 @@ describe("spidermonkey export/import sanity test", function() {
|
||||
|
||||
eval(stdout);
|
||||
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();
|
||||
});
|
||||
|
||||
@@ -65,9 +65,9 @@ describe("Yield", function() {
|
||||
);
|
||||
});
|
||||
|
||||
it("Should not allow yield to be used as symbol, identifier or property outside generators in strict mode", function() {
|
||||
it("Should not allow yield to be used as symbol, identifier or shorthand property outside generators in strict mode", function() {
|
||||
var tests = [
|
||||
// Fail as as_symbol
|
||||
// Fail in as_symbol
|
||||
'"use strict"; import yield from "bar";',
|
||||
'"use strict"; yield = 123;',
|
||||
'"use strict"; yield: "123";',
|
||||
@@ -79,13 +79,12 @@ describe("Yield", function() {
|
||||
'"use strict"; var yield = "foo";',
|
||||
'"use strict"; class yield {}',
|
||||
|
||||
// Fail as maybe_assign
|
||||
// Fail in maybe_assign
|
||||
'"use strict"; var foo = yield;',
|
||||
'"use strict"; var foo = bar = yield',
|
||||
|
||||
// Fail as as_property_name
|
||||
// Fail in as_property_name
|
||||
'"use strict"; var foo = {yield};',
|
||||
'"use strict"; var bar = {yield: "foo"};'
|
||||
];
|
||||
|
||||
var fail = function(e) {
|
||||
@@ -103,4 +102,4 @@ describe("Yield", function() {
|
||||
assert.throws(test(tests[i]), fail, tests[i]);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,103 +1,73 @@
|
||||
// Testing UglifyJS <-> SpiderMonkey AST conversion
|
||||
// through generative testing.
|
||||
"use strict";
|
||||
|
||||
var UglifyJS = require("./node"),
|
||||
escodegen = require("escodegen"),
|
||||
esfuzz = require("esfuzz"),
|
||||
estraverse = require("estraverse"),
|
||||
prefix = "\r ";
|
||||
var acorn = require("acorn");
|
||||
var ufuzz = require("./ufuzz");
|
||||
var UglifyJS = require("..");
|
||||
|
||||
// Normalizes input AST for UglifyJS in order to get correct comparison.
|
||||
|
||||
function normalizeInput(ast) {
|
||||
return estraverse.replace(ast, {
|
||||
enter: function(node, parent) {
|
||||
switch (node.type) {
|
||||
// Internally mark all the properties with semi-standard type "Property".
|
||||
case "ObjectExpression":
|
||||
node.properties.forEach(function (property) {
|
||||
property.type = "Property";
|
||||
});
|
||||
break;
|
||||
|
||||
// Since UglifyJS doesn"t recognize different types of property keys,
|
||||
// decision on SpiderMonkey node type is based on check whether key
|
||||
// can be valid identifier or not - so we do in input AST.
|
||||
case "Property":
|
||||
var key = node.key;
|
||||
if (key.type === "Literal" && typeof key.value === "string" && UglifyJS.is_identifier(key.value)) {
|
||||
node.key = {
|
||||
type: "Identifier",
|
||||
name: key.value
|
||||
};
|
||||
} else if (key.type === "Identifier" && !UglifyJS.is_identifier(key.name)) {
|
||||
node.key = {
|
||||
type: "Literal",
|
||||
value: key.name
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
// UglifyJS internally flattens all the expression sequences - either
|
||||
// to one element (if sequence contains only one element) or flat list.
|
||||
case "SequenceExpression":
|
||||
node.expressions = node.expressions.reduce(function flatten(list, expr) {
|
||||
return list.concat(expr.type === "SequenceExpression" ? expr.expressions.reduce(flatten, []) : [expr]);
|
||||
}, []);
|
||||
if (node.expressions.length === 1) {
|
||||
return node.expressions[0];
|
||||
}
|
||||
break;
|
||||
}
|
||||
function try_beautify(code) {
|
||||
var beautified = UglifyJS.minify(code, {
|
||||
compress: false,
|
||||
mangle: false,
|
||||
output: {
|
||||
beautify: true,
|
||||
bracketize: true
|
||||
}
|
||||
});
|
||||
if (beautified.error) {
|
||||
console.log("// !!! beautify failed !!!");
|
||||
console.log(beautified.error.stack);
|
||||
console.log(code);
|
||||
} else {
|
||||
console.log("// (beautified)");
|
||||
console.log(beautified.code);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function(options) {
|
||||
console.log("--- UglifyJS <-> Mozilla AST conversion");
|
||||
|
||||
for (var counter = 0; counter < options.iterations; counter++) {
|
||||
process.stdout.write(prefix + counter + "/" + options.iterations);
|
||||
|
||||
var ast1 = normalizeInput(esfuzz.generate({
|
||||
maxDepth: options.maxDepth
|
||||
}));
|
||||
|
||||
var ast2 =
|
||||
UglifyJS
|
||||
.AST_Node
|
||||
.from_mozilla_ast(ast1)
|
||||
.to_mozilla_ast();
|
||||
|
||||
var astPair = [
|
||||
{name: 'expected', value: ast1},
|
||||
{name: 'actual', value: ast2}
|
||||
];
|
||||
|
||||
var jsPair = astPair.map(function(item) {
|
||||
return {
|
||||
name: item.name,
|
||||
value: escodegen.generate(item.value)
|
||||
}
|
||||
});
|
||||
|
||||
if (jsPair[0].value !== jsPair[1].value) {
|
||||
var fs = require("fs");
|
||||
var acorn = require("acorn");
|
||||
|
||||
fs.existsSync("tmp") || fs.mkdirSync("tmp");
|
||||
|
||||
jsPair.forEach(function (item) {
|
||||
var fileName = "tmp/dump_" + item.name;
|
||||
var ast = acorn.parse(item.value);
|
||||
fs.writeFileSync(fileName + ".js", item.value);
|
||||
fs.writeFileSync(fileName + ".json", JSON.stringify(ast, null, 2));
|
||||
});
|
||||
|
||||
process.stdout.write("\n");
|
||||
throw new Error("Got different outputs, check out tmp/dump_*.{js,json} for codes and ASTs.");
|
||||
function test(original, estree, description) {
|
||||
var transformed = UglifyJS.minify(UglifyJS.AST_Node.from_mozilla_ast(estree), {
|
||||
compress: false,
|
||||
mangle: false
|
||||
});
|
||||
if (transformed.error || original !== transformed.code) {
|
||||
console.log("//=============================================================");
|
||||
console.log("// !!!!!! Failed... round", round);
|
||||
console.log("// original code");
|
||||
try_beautify(original);
|
||||
console.log();
|
||||
console.log();
|
||||
console.log("//-------------------------------------------------------------");
|
||||
console.log("//", description);
|
||||
if (transformed.error) {
|
||||
console.log(transformed.error.stack);
|
||||
} else {
|
||||
try_beautify(transformed.code);
|
||||
}
|
||||
console.log("!!!!!! Failed... round", round);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
process.stdout.write(prefix + "Probability of error is less than " + (100 / options.iterations) + "%, stopping.\n");
|
||||
};
|
||||
var num_iterations = ufuzz.num_iterations;
|
||||
for (var round = 1; round <= num_iterations; round++) {
|
||||
process.stdout.write(round + " of " + num_iterations + "\r");
|
||||
var code = ufuzz.createTopLevelCode();
|
||||
var uglified = UglifyJS.minify(code, {
|
||||
compress: false,
|
||||
mangle: false,
|
||||
output: {
|
||||
ast: true
|
||||
}
|
||||
});
|
||||
test(uglified.code, uglified.ast.to_mozilla_ast(), "AST_Node.to_mozilla_ast()");
|
||||
try {
|
||||
test(uglified.code, acorn.parse(code), "acorn.parse()");
|
||||
} catch (e) {
|
||||
console.log("//=============================================================");
|
||||
console.log("// acorn parser failed... round", round);
|
||||
console.log(e);
|
||||
console.log("// original code");
|
||||
console.log(code);
|
||||
}
|
||||
}
|
||||
console.log();
|
||||
|
||||
@@ -5,6 +5,7 @@ var path = require("path");
|
||||
var fs = require("fs");
|
||||
var assert = require("assert");
|
||||
var sandbox = require("./sandbox");
|
||||
var semver = require("semver");
|
||||
|
||||
var tests_dir = path.dirname(module.filename);
|
||||
var failures = 0;
|
||||
@@ -23,12 +24,6 @@ mocha_tests();
|
||||
var run_sourcemaps_tests = require('./sourcemaps');
|
||||
run_sourcemaps_tests();
|
||||
|
||||
var run_ast_conversion_tests = require("./mozilla-ast");
|
||||
|
||||
run_ast_conversion_tests({
|
||||
iterations: 1000
|
||||
});
|
||||
|
||||
/* -----[ utils ]----- */
|
||||
|
||||
function tmpl() {
|
||||
@@ -170,7 +165,8 @@ function run_compress_tests() {
|
||||
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);
|
||||
if (test.expect_stdout === true) {
|
||||
test.expect_stdout = stdout;
|
||||
@@ -280,7 +276,14 @@ function parse_test(file) {
|
||||
if (node instanceof U.AST_LabeledStatement) {
|
||||
var label = node.label;
|
||||
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}]", {
|
||||
name: label.name,
|
||||
line: label.start.line,
|
||||
@@ -288,7 +291,7 @@ function parse_test(file) {
|
||||
})
|
||||
);
|
||||
var stat = node.body;
|
||||
if (label.name == "expect_exact") {
|
||||
if (label.name == "expect_exact" || label.name == "node_version") {
|
||||
test[label.name] = read_string(stat);
|
||||
} else if (label.name == "expect_stdout") {
|
||||
if (stat.TYPE == "SimpleStatement" && stat.body instanceof U.AST_Boolean) {
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
var semver = require("semver");
|
||||
var vm = require("vm");
|
||||
|
||||
function safe_log(arg) {
|
||||
function safe_log(arg, level) {
|
||||
if (arg) switch (typeof arg) {
|
||||
case "function":
|
||||
return arg.toString();
|
||||
case "object":
|
||||
if (/Error$/.test(arg.name)) return arg.toString();
|
||||
arg.constructor.toString();
|
||||
for (var key in arg) {
|
||||
arg[key] = safe_log(arg[key]);
|
||||
if (level--) for (var key in arg) {
|
||||
if (!Object.getOwnPropertyDescriptor(arg, key).get) {
|
||||
arg[key] = safe_log(arg[key], level);
|
||||
}
|
||||
}
|
||||
}
|
||||
return arg;
|
||||
@@ -48,7 +51,9 @@ exports.run_code = function(code) {
|
||||
].join("\n"), {
|
||||
console: {
|
||||
log: function() {
|
||||
return console.log.apply(console, [].map.call(arguments, safe_log));
|
||||
return console.log.apply(console, [].map.call(arguments, function(arg) {
|
||||
return safe_log(arg, 3);
|
||||
}));
|
||||
}
|
||||
}
|
||||
}, { timeout: 5000 });
|
||||
@@ -59,7 +64,7 @@ exports.run_code = function(code) {
|
||||
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 != "string") {
|
||||
if (expected.name != actual.name) return false;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
var UglifyJS = require("./node");
|
||||
var UglifyJS = require("..");
|
||||
var ok = require("assert");
|
||||
|
||||
module.exports = function () {
|
||||
@@ -26,11 +26,11 @@ module.exports = function () {
|
||||
}
|
||||
|
||||
function source_map(js) {
|
||||
var source_map = UglifyJS.SourceMap();
|
||||
var stream = UglifyJS.OutputStream({ source_map: source_map });
|
||||
var parsed = UglifyJS.parse(js);
|
||||
parsed.print(stream);
|
||||
return JSON.parse(source_map.toString());
|
||||
return JSON.parse(UglifyJS.minify(js, {
|
||||
compress: false,
|
||||
mangle: false,
|
||||
sourceMap: true
|
||||
}).map);
|
||||
}
|
||||
|
||||
// Run standalone
|
||||
|
||||
218
test/ufuzz.js
218
test/ufuzz.js
@@ -48,8 +48,9 @@ var STMT_COUNT_FROM_GLOBAL = true; // count statement depth from nearest functio
|
||||
var num_iterations = +process.argv[2] || 1/0;
|
||||
var verbose = false; // log every generated test
|
||||
var verbose_interval = false; // log every 100 generated tests
|
||||
var verbose_error = false;
|
||||
var use_strict = false;
|
||||
var catch_redef = require.main === module;
|
||||
var generate_directive = require.main === module;
|
||||
for (var i = 2; i < process.argv.length; ++i) {
|
||||
switch (process.argv[i]) {
|
||||
case '-v':
|
||||
@@ -58,9 +59,6 @@ for (var i = 2; i < process.argv.length; ++i) {
|
||||
case '-V':
|
||||
verbose_interval = true;
|
||||
break;
|
||||
case '-E':
|
||||
verbose_error = true;
|
||||
break;
|
||||
case '-t':
|
||||
MAX_GENERATED_TOPLEVELS_PER_RUN = +process.argv[++i];
|
||||
if (!MAX_GENERATED_TOPLEVELS_PER_RUN) throw new Error('Must generate at least one toplevel per run');
|
||||
@@ -79,6 +77,12 @@ for (var i = 2; i < process.argv.length; ++i) {
|
||||
STMT_SECOND_LEVEL_OVERRIDE = STMT_ARG_TO_ID[name];
|
||||
if (!(STMT_SECOND_LEVEL_OVERRIDE >= 0)) throw new Error('Unknown statement name; use -? to get a list');
|
||||
break;
|
||||
case '--no-catch-redef':
|
||||
catch_redef = false;
|
||||
break;
|
||||
case '--no-directive':
|
||||
generate_directive = false;
|
||||
break;
|
||||
case '--use-strict':
|
||||
use_strict = true;
|
||||
break;
|
||||
@@ -103,11 +107,12 @@ for (var i = 2; i < process.argv.length; ++i) {
|
||||
console.log('<number>: generate this many cases (if used must be first arg)');
|
||||
console.log('-v: print every generated test case');
|
||||
console.log('-V: print every 100th generated test case');
|
||||
console.log('-E: print generated test case with runtime error');
|
||||
console.log('-t <int>: generate this many toplevels per run (more take longer)');
|
||||
console.log('-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)');
|
||||
console.log('-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');
|
||||
console.log('--no-directive: do not generate directives');
|
||||
console.log('--use-strict: generate "use strict"');
|
||||
console.log('--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');
|
||||
@@ -192,12 +197,33 @@ var ASSIGNMENTS = [
|
||||
'=',
|
||||
'=',
|
||||
'=',
|
||||
'=',
|
||||
'=',
|
||||
'=',
|
||||
'=',
|
||||
|
||||
'=',
|
||||
'=',
|
||||
'=',
|
||||
'=',
|
||||
'=',
|
||||
'=',
|
||||
'=',
|
||||
'=',
|
||||
'=',
|
||||
'=',
|
||||
|
||||
'==',
|
||||
'!=',
|
||||
'===',
|
||||
'!==',
|
||||
'+=',
|
||||
'+=',
|
||||
'+=',
|
||||
'+=',
|
||||
'+=',
|
||||
'+=',
|
||||
'+=',
|
||||
'+=',
|
||||
'+=',
|
||||
'+=',
|
||||
|
||||
'-=',
|
||||
'*=',
|
||||
'/=',
|
||||
@@ -207,7 +233,8 @@ var ASSIGNMENTS = [
|
||||
'<<=',
|
||||
'>>=',
|
||||
'>>>=',
|
||||
'%=' ];
|
||||
'%=',
|
||||
];
|
||||
|
||||
var UNARY_SAFE = [
|
||||
'+',
|
||||
@@ -276,6 +303,7 @@ var TYPEOF_OUTCOMES = [
|
||||
'symbol',
|
||||
'crap' ];
|
||||
|
||||
var unique_vars = [];
|
||||
var loops = 0;
|
||||
var funcs = 0;
|
||||
var labels = 10000;
|
||||
@@ -290,6 +318,10 @@ function strictMode() {
|
||||
}
|
||||
|
||||
function createTopLevelCode() {
|
||||
VAR_NAMES.length = INITIAL_NAMES_LEN; // prune any previous names still in the list
|
||||
unique_vars.length = 0;
|
||||
loops = 0;
|
||||
funcs = 0;
|
||||
return [
|
||||
strictMode(),
|
||||
'var a = 100, b = 10, c = 0;',
|
||||
@@ -325,33 +357,36 @@ function createArgs() {
|
||||
return args.join(', ');
|
||||
}
|
||||
|
||||
function filterDirective(s) {
|
||||
if (!generate_directive && !s[1] && /\("/.test(s[2])) s[2] = ';' + s[2];
|
||||
return s;
|
||||
}
|
||||
|
||||
function createFunction(recurmax, inGlobal, noDecl, canThrow, stmtDepth) {
|
||||
if (--recurmax < 0) { return ';'; }
|
||||
if (!STMT_COUNT_FROM_GLOBAL) stmtDepth = 0;
|
||||
var func = funcs++;
|
||||
var namesLenBefore = VAR_NAMES.length;
|
||||
var name = (inGlobal || rng(5) > 0) ? 'f' + func : createVarName(MANDATORY, noDecl);
|
||||
if (name === 'a' || name === 'b' || name === 'c') name = 'f' + func; // quick hack to prevent assignment to func names of being called
|
||||
var s = '';
|
||||
var name;
|
||||
if (inGlobal || rng(5) > 0) name = 'f' + func;
|
||||
else {
|
||||
unique_vars.push('a', 'b', 'c');
|
||||
name = createVarName(MANDATORY, noDecl);
|
||||
unique_vars.length -= 3;
|
||||
}
|
||||
var s = [
|
||||
'function ' + name + '(' + createParams() + '){',
|
||||
strictMode()
|
||||
];
|
||||
if (rng(5) === 0) {
|
||||
// functions with functions. lower the recursion to prevent a mess.
|
||||
s = [
|
||||
'function ' + name + '(' + createParams() + '){',
|
||||
strictMode(),
|
||||
createFunctions(rng(5) + 1, Math.ceil(recurmax * 0.7), NOT_GLOBAL, ANY_TYPE, canThrow, stmtDepth),
|
||||
'}',
|
||||
''
|
||||
].join('\n');
|
||||
s.push(createFunctions(rng(5) + 1, Math.ceil(recurmax * 0.7), NOT_GLOBAL, ANY_TYPE, canThrow, stmtDepth));
|
||||
} else {
|
||||
// functions with statements
|
||||
s = [
|
||||
'function ' + name + '(' + createParams() + '){',
|
||||
strictMode(),
|
||||
createStatements(3, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
|
||||
'}',
|
||||
''
|
||||
].join('\n');
|
||||
s.push(createStatements(3, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth));
|
||||
}
|
||||
s.push('}', '');
|
||||
s = filterDirective(s).join('\n');
|
||||
|
||||
VAR_NAMES.length = namesLenBefore;
|
||||
|
||||
@@ -359,7 +394,6 @@ function createFunction(recurmax, inGlobal, noDecl, canThrow, stmtDepth) {
|
||||
// avoid "function statements" (decl inside statements)
|
||||
else if (inGlobal || rng(10) > 0) s += 'var ' + createVarName(MANDATORY) + ' = ' + name + '(' + createArgs() + ');';
|
||||
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -406,7 +440,7 @@ function getLabel(label) {
|
||||
return label && " L" + label;
|
||||
}
|
||||
|
||||
function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) {
|
||||
function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth, target) {
|
||||
++stmtDepth;
|
||||
var loop = ++loops;
|
||||
if (--recurmax < 0) {
|
||||
@@ -414,10 +448,11 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
|
||||
}
|
||||
|
||||
// allow to forcefully generate certain structures at first or second recursion level
|
||||
var target = 0;
|
||||
if (stmtDepth === 1 && STMT_FIRST_LEVEL_OVERRIDE >= 0) target = STMT_FIRST_LEVEL_OVERRIDE;
|
||||
else if (stmtDepth === 2 && STMT_SECOND_LEVEL_OVERRIDE >= 0) target = STMT_SECOND_LEVEL_OVERRIDE;
|
||||
else target = STMTS_TO_USE[rng(STMTS_TO_USE.length)];
|
||||
if (target === undefined) {
|
||||
if (stmtDepth === 1 && STMT_FIRST_LEVEL_OVERRIDE >= 0) target = STMT_FIRST_LEVEL_OVERRIDE;
|
||||
else if (stmtDepth === 2 && STMT_SECOND_LEVEL_OVERRIDE >= 0) target = STMT_SECOND_LEVEL_OVERRIDE;
|
||||
else target = STMTS_TO_USE[rng(STMTS_TO_USE.length)];
|
||||
}
|
||||
|
||||
switch (target) {
|
||||
case STMT_BLOCK:
|
||||
@@ -460,20 +495,22 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
|
||||
case STMT_VAR:
|
||||
switch (rng(3)) {
|
||||
case 0:
|
||||
unique_vars.push('c');
|
||||
var name = createVarName(MANDATORY);
|
||||
if (name === 'c') name = 'a';
|
||||
unique_vars.pop();
|
||||
return 'var ' + name + ';';
|
||||
case 1:
|
||||
// initializer can only have one expression
|
||||
unique_vars.push('c');
|
||||
var name = createVarName(MANDATORY);
|
||||
if (name === 'c') name = 'b';
|
||||
unique_vars.pop();
|
||||
return 'var ' + name + ' = ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
|
||||
default:
|
||||
// initializer can only have one expression
|
||||
unique_vars.push('c');
|
||||
var n1 = createVarName(MANDATORY);
|
||||
if (n1 === 'c') n1 = 'b';
|
||||
var n2 = createVarName(MANDATORY);
|
||||
if (n2 === 'c') n2 = 'b';
|
||||
unique_vars.pop();
|
||||
return 'var ' + n1 + ' = ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ', ' + n2 + ' = ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
|
||||
}
|
||||
case STMT_RETURN_ETC:
|
||||
@@ -514,8 +551,11 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
|
||||
var nameLenBefore = VAR_NAMES.length;
|
||||
var catchName = createVarName(MANDATORY);
|
||||
var freshCatchName = VAR_NAMES.length !== nameLenBefore;
|
||||
if (!catch_redef) unique_vars.push(catchName);
|
||||
s += ' catch (' + catchName + ') { ' + createStatements(3, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + ' }';
|
||||
if (freshCatchName) VAR_NAMES.splice(nameLenBefore, 1); // remove catch name
|
||||
// remove catch name
|
||||
if (!catch_redef) unique_vars.pop();
|
||||
if (freshCatchName) VAR_NAMES.splice(nameLenBefore, 1);
|
||||
}
|
||||
if (n !== 0) s += ' finally { ' + createStatements(3, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + ' }';
|
||||
return s;
|
||||
@@ -593,8 +633,9 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
|
||||
case p++:
|
||||
case p++:
|
||||
var nameLenBefore = VAR_NAMES.length;
|
||||
unique_vars.push('c');
|
||||
var name = createVarName(MAYBE); // note: this name is only accessible from _within_ the function. and immutable at that.
|
||||
if (name == 'c') name = 'a';
|
||||
unique_vars.pop();
|
||||
var s = [];
|
||||
switch (rng(5)) {
|
||||
case 0:
|
||||
@@ -636,7 +677,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
|
||||
strictMode()
|
||||
);
|
||||
if (instantiate) for (var i = rng(4); --i >= 0;) {
|
||||
if (rng(2)) s.push('this.' + getDotKey() + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ';');
|
||||
if (rng(2)) s.push('this.' + getDotKey(true) + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ';');
|
||||
else s.push('this[' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']' + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ';');
|
||||
}
|
||||
s.push(
|
||||
@@ -646,7 +687,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
|
||||
break;
|
||||
}
|
||||
VAR_NAMES.length = nameLenBefore;
|
||||
return s.join('\n');
|
||||
return filterDirective(s).join('\n');
|
||||
case p++:
|
||||
case p++:
|
||||
return createTypeofExpr(recurmax, stmtDepth, canThrow);
|
||||
@@ -689,19 +730,19 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
|
||||
") || " + rng(10) + ").toString()[" +
|
||||
createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "] ";
|
||||
case p++:
|
||||
return createArrayLiteral(recurmax, COMMA_OK, stmtDepth, canThrow);
|
||||
return createArrayLiteral(recurmax, stmtDepth, canThrow);
|
||||
case p++:
|
||||
return createObjectLiteral(recurmax, COMMA_OK, stmtDepth, canThrow);
|
||||
return createObjectLiteral(recurmax, stmtDepth, canThrow);
|
||||
case p++:
|
||||
return createArrayLiteral(recurmax, COMMA_OK, stmtDepth, canThrow) + '[' +
|
||||
return createArrayLiteral(recurmax, stmtDepth, canThrow) + '[' +
|
||||
createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']';
|
||||
case p++:
|
||||
return createObjectLiteral(recurmax, COMMA_OK, stmtDepth, canThrow) + '[' +
|
||||
return createObjectLiteral(recurmax, stmtDepth, canThrow) + '[' +
|
||||
createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']';
|
||||
case p++:
|
||||
return createArrayLiteral(recurmax, COMMA_OK, stmtDepth, canThrow) + '.' + getDotKey();
|
||||
return createArrayLiteral(recurmax, stmtDepth, canThrow) + '.' + getDotKey();
|
||||
case p++:
|
||||
return createObjectLiteral(recurmax, COMMA_OK, stmtDepth, canThrow) + '.' + getDotKey();
|
||||
return createObjectLiteral(recurmax, stmtDepth, canThrow) + '.' + getDotKey();
|
||||
case p++:
|
||||
var name = getVarName();
|
||||
return name + ' && ' + name + '[' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']';
|
||||
@@ -713,7 +754,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
|
||||
return _createExpression(recurmax, noComma, stmtDepth, canThrow);
|
||||
}
|
||||
|
||||
function createArrayLiteral(recurmax, noComma, stmtDepth, canThrow) {
|
||||
function createArrayLiteral(recurmax, stmtDepth, canThrow) {
|
||||
recurmax--;
|
||||
var arr = "[";
|
||||
for (var i = rng(6); --i >= 0;) {
|
||||
@@ -746,18 +787,56 @@ var KEYS = [
|
||||
"3",
|
||||
].concat(SAFE_KEYS);
|
||||
|
||||
function getDotKey() {
|
||||
return SAFE_KEYS[rng(SAFE_KEYS.length)];
|
||||
function getDotKey(assign) {
|
||||
var key;
|
||||
do {
|
||||
key = SAFE_KEYS[rng(SAFE_KEYS.length)];
|
||||
} while (assign && key == "length");
|
||||
return key;
|
||||
}
|
||||
|
||||
function createObjectLiteral(recurmax, noComma, stmtDepth, canThrow) {
|
||||
recurmax--;
|
||||
var obj = "({";
|
||||
for (var i = rng(6); --i >= 0;) {
|
||||
var key = KEYS[rng(KEYS.length)];
|
||||
obj += key + ":(" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "), ";
|
||||
function createAccessor(recurmax, stmtDepth, canThrow) {
|
||||
var namesLenBefore = VAR_NAMES.length;
|
||||
var s;
|
||||
var prop1 = getDotKey();
|
||||
if (rng(2) == 0) {
|
||||
s = [
|
||||
'get ' + prop1 + '(){',
|
||||
strictMode(),
|
||||
createStatements(2, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
|
||||
createStatement(recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth, STMT_RETURN_ETC),
|
||||
'},'
|
||||
];
|
||||
} else {
|
||||
var prop2;
|
||||
do {
|
||||
prop2 = getDotKey();
|
||||
} while (prop1 == prop2);
|
||||
s = [
|
||||
'set ' + prop1 + '(' + createVarName(MANDATORY) + '){',
|
||||
strictMode(),
|
||||
createStatements(2, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
|
||||
'this.' + prop2 + createAssignment() + _createBinaryExpr(recurmax, COMMA_OK, stmtDepth, canThrow) + ';',
|
||||
'},'
|
||||
];
|
||||
}
|
||||
return obj + "})";
|
||||
VAR_NAMES.length = namesLenBefore;
|
||||
return filterDirective(s).join('\n');
|
||||
}
|
||||
|
||||
function createObjectLiteral(recurmax, stmtDepth, canThrow) {
|
||||
recurmax--;
|
||||
var obj = ['({'];
|
||||
for (var i = rng(6); --i >= 0;) {
|
||||
if (rng(20) == 0) {
|
||||
obj.push(createAccessor(recurmax, stmtDepth, canThrow));
|
||||
} else {
|
||||
var key = KEYS[rng(KEYS.length)];
|
||||
obj.push(key + ':(' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + '),');
|
||||
}
|
||||
}
|
||||
obj.push('})');
|
||||
return obj.join('\n');
|
||||
}
|
||||
|
||||
function createNestedBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
|
||||
@@ -787,7 +866,7 @@ function _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
|
||||
return canThrow && rng(10) == 0 ? expr : '(' + assignee + ' && ' + expr + ')';
|
||||
case 4:
|
||||
assignee = getVarName();
|
||||
expr = '(' + assignee + '.' + getDotKey() + createAssignment()
|
||||
expr = '(' + assignee + '.' + getDotKey(true) + createAssignment()
|
||||
+ _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')';
|
||||
return canThrow && rng(10) == 0 ? expr : '(' + assignee + ' && ' + expr + ')';
|
||||
default:
|
||||
@@ -844,17 +923,24 @@ function getVarName() {
|
||||
|
||||
function createVarName(maybe, dontStore) {
|
||||
if (!maybe || rng(2)) {
|
||||
var name = VAR_NAMES[rng(VAR_NAMES.length)];
|
||||
var suffix = rng(3);
|
||||
if (suffix) {
|
||||
name += '_' + suffix;
|
||||
if (!dontStore) VAR_NAMES.push(name);
|
||||
}
|
||||
var name;
|
||||
do {
|
||||
name = VAR_NAMES[rng(VAR_NAMES.length)];
|
||||
if (suffix) name += '_' + suffix;
|
||||
} while (unique_vars.indexOf(name) >= 0);
|
||||
if (suffix && !dontStore) VAR_NAMES.push(name);
|
||||
return name;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
if (require.main !== module) {
|
||||
exports.createTopLevelCode = createTopLevelCode;
|
||||
exports.num_iterations = num_iterations;
|
||||
return;
|
||||
}
|
||||
|
||||
function try_beautify(code, result) {
|
||||
var beautified = UglifyJS.minify(code, {
|
||||
compress: false,
|
||||
@@ -973,10 +1059,6 @@ var uglify_code, uglify_result, ok;
|
||||
for (var round = 1; round <= num_iterations; round++) {
|
||||
process.stdout.write(round + " of " + num_iterations + "\r");
|
||||
|
||||
VAR_NAMES.length = INITIAL_NAMES_LEN; // prune any previous names still in the list
|
||||
loops = 0;
|
||||
funcs = 0;
|
||||
|
||||
original_code = createTopLevelCode();
|
||||
original_result = sandbox.run_code(original_code);
|
||||
(typeof original_result != "string" ? fallback_options : minify_options).forEach(function(options) {
|
||||
@@ -992,7 +1074,7 @@ for (var round = 1; round <= num_iterations; round++) {
|
||||
}
|
||||
}
|
||||
if (verbose || (verbose_interval && !(round % INTERVAL_COUNT)) || !ok) log(options);
|
||||
else if (verbose_error && typeof original_result != "string") {
|
||||
else if (typeof original_result != "string") {
|
||||
console.log("//=============================================================");
|
||||
console.log("// original code");
|
||||
try_beautify(original_code, original_result);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
exports["Dictionary"] = Dictionary;
|
||||
exports["TreeWalker"] = TreeWalker;
|
||||
exports["TreeTransformer"] = TreeTransformer;
|
||||
exports["minify"] = minify;
|
||||
exports["_push_uniq"] = push_uniq;
|
||||
|
||||
Reference in New Issue
Block a user