Compare commits

...

52 Commits

Author SHA1 Message Date
Alex Lam S.L
756c9aa7dc keep minify() options in sync (#1940) 2017-05-15 20:29:48 +08:00
Alex Lam S.L
07d6bfd707 Merge pull request #1939 from alexlamsl/harmony-v3.0.5
Merging from master for 3.0.5
2017-05-15 19:48:00 +08:00
alexlamsl
81243c4e71 Merge branch 'master' into harmony-v3.0.5 2017-05-15 18:58:54 +08:00
alexlamsl
cd6e849555 Revert "remove support for const (#1910)"
This reverts commit c391576d52.
2017-05-15 18:38:16 +08:00
Alex Lam S.L
ff526be61d v3.0.5 2017-05-15 11:37:14 +08:00
Alex Lam S.L
e005099fb1 fix & improve coverage of estree (#1935)
- fix `estree` conversion of getter/setter
- fix non-directive literal in `to_mozilla_ast()`
- revamp `test/mozilla-ast.js`
  - reuse `test/ufuzz.js` for code generation
  - use `acorn.parse()` for creating `estree`
- extend `test/ufuzz.js` for `acorn` workaround
  - catch variable redefinition
  - non-trivial literal as directive
  - adjust options for tolerance

Miscellaneous
- optional semi-colon when parsing directives

fixes #1914
closes #1915
2017-05-15 02:37:53 +08:00
kzc
504a436e9d Tweak README Notes (#1934) 2017-05-14 02:12:14 +08:00
Alex Lam S.L
3ca902258c fix bugs with getter/setter (#1926)
- `reduce_vars`
- `side_effects`
- property access for object
- `AST_SymbolAccessor` as key names

enhance `test/ufuzz.js`
- add object getter & setter
  - property assignment to setter
  - avoid infinite recursion in setter
- fix & adjust assignment operators
  - 50% `=`
  - 25% `+=`
  - 2.5% each for the rest
- avoid "Invalid array length"
- fix `console.log()`
  - bypass getter
  - curb recursive reference
- deprecate `-E`, always report runtime errors
2017-05-14 02:10:34 +08:00
kzc
91de285166 uglify-es: update homepage in package.json (#1933)
to point to harmony branch on github
2017-05-14 00:25:06 +08:00
kzc
4d8f289eb0 fix export default expression; (#1932) 2017-05-13 12:56:46 +08:00
olsonpm
fd0951231c document 3 max passes (#1928) 2017-05-13 12:54:32 +08:00
olsonpm
9e29b6dad2 clarify wording (#1931) 2017-05-13 12:54:01 +08:00
Alex Lam S.L
c391576d52 remove support for const (#1910)
As this is not part of ES5.
2017-05-12 14:57:41 +08:00
Alex Lam S.L
ac73c5d421 avoid arguments and eval in reduce_vars (#1924)
fixes #1922
2017-05-12 12:34:55 +08:00
olsonpm
547f41beba add documentation for side_effects & [#@]__PURE__ (#1925) 2017-05-12 12:29:55 +08:00
Alex Lam S.L
945ba64160 Merge pull request #1923 from alexlamsl/harmony-v3.0.4
Merging from master for 3.0.4
2017-05-12 06:52:21 +08:00
Anthony Van de Gejuchte
c699200398 Make sure globals can be accessed from the browser (#1920)
Note: no tests as there are no integration tests
2017-05-12 05:50:35 +08:00
alexlamsl
daf44f2b21 Merge branch 'master' into harmony-v3.0.4 2017-05-12 05:13:11 +08:00
Alex Lam S.L
daaefc17b9 v3.0.4 2017-05-12 04:52:39 +08:00
Alex Lam S.L
1d407e761e fix invalid transform on const (#1919)
- preserve (re)assignment to `const` for runtime error
- suppress `cascade` on `const`, as runtime behaviour is ill-defined
2017-05-12 04:51:44 +08:00
kzc
2b44f4ae30 update README (#1918) 2017-05-12 03:36:33 +08:00
Alexis Tyler
e51c3541da fix typo (#1913) 2017-05-11 20:24:33 +08:00
Alex Lam S.L
3bf194684b update documentation (#1909)
- clarify options on `--source-map`
- fix `minify()` examples

fixes #1905
2017-05-11 17:50:50 +08:00
Gyusun Yeom
fcd90db30d fix safari syntax error - declare twice (#1851)
To avoid Safari bug, scope of for loop should enclose parent scope variables.


fixes #1753
2017-05-11 16:48:43 +08:00
Alex Lam S.L
e2888bdc43 Merge pull request #1901 from alexlamsl/harmony-v3.0.3
Merging from master for 3.0.3
2017-05-10 14:26:58 +08:00
alexlamsl
fb50b7b627 Merge branch 'master' into harmony-v3.0.3 2017-05-10 11:52:59 +08:00
Alex Lam S.L
aae7d49d0c v3.0.3 2017-05-10 11:45:03 +08:00
kzc
9d59c693c2 fix for-of loop with const iterator (#1899) 2017-05-10 11:36:03 +08:00
kzc
0459af2ecc Update issue template: change harmony to uglify-es (#1900) 2017-05-10 11:07:54 +08:00
kzc
04f2344efc Remove unnecessary git clone instructions in README (#1897) 2017-05-10 11:06:50 +08:00
kzc
6ddb5bd94d Remove incorrect git clone instructions from uglify-es README (#1896) 2017-05-10 11:06:22 +08:00
kzc
bad9d5cf88 Change harmony to uglify-es in master README (#1895) 2017-05-10 05:07:45 +08:00
kzc
eda49605c5 Have harmony docs use uglify-es package name. (#1894) 2017-05-10 04:41:09 +08:00
Alex Lam S.L
a0f5f862df gracefully handle non-Error being thrown (#1893) 2017-05-10 04:20:59 +08:00
Alex Lam S.L
1e9ef17e32 Merge pull request #1892 from alexlamsl/harmony-v3.0.2
Merging from master for 3.0.2
2017-05-10 03:14:45 +08:00
Alex Lam S.L
41996be86f extend test timeout
Travis has gone a lot slower recently, and most test failures are due to time-out on this particular test.
2017-05-10 02:43:12 +08:00
alexlamsl
222100ea4c Merge branch 'master' into harmony-v3.0.2 2017-05-10 01:57:32 +08:00
Alex Lam S.L
5fd8244a2e v3.0.2 2017-05-10 01:52:00 +08:00
Alex Lam S.L
93db48a317 rename package 2017-05-10 01:46:55 +08:00
Alex Lam S.L
2944e3df7d fix collapse_vars on destructuring declarations (#1889)
fixes #1886
2017-05-09 17:44:28 +08:00
Alex Lam S.L
c14e280585 print error stack in CLI (#1890) 2017-05-09 16:36:44 +08:00
Alex Lam S.L
bc3fa78e8c mention minify().error 2017-05-09 16:09:48 +08:00
Alex Lam S.L
8c7c107765 update minify() usage in test/ufuzz.js (#1888)
fixes #1887
2017-05-09 15:58:46 +08:00
Alex Lam S.L
e0ae8da089 Merge pull request #1885 from alexlamsl/harmony-v3.0.1
Merging from master for 3.0.1
2017-05-09 02:49:28 +08:00
alexlamsl
81f1311b24 Merge branch 'master' into harmony-v3.0.1 2017-05-09 02:10:06 +08:00
Alex Lam S.L
2433bb4e52 fix Unicode handling in parser (#1884)
There was an implicit assumption that first character within surrogate header range implies the next character must form a surrogate pair, which is not necessarily true.
2017-05-09 01:58:31 +08:00
kzc
3dd328dce3 [3.x] fix documentation for beautify options (#1882)
- use underscores rather than dashes.
2017-05-08 23:06:56 +08:00
Alex Lam S.L
014f428153 v3.0.1 2017-05-08 07:05:57 +08:00
Alex Lam S.L
a3b2eb75bd return Error from minify() (#1880)
Have `minify()` return `Error` in `result.error` rather than throwing it.
2017-05-08 07:05:19 +08:00
Alex Lam S.L
da295de82b support dumping AST (#1879)
Re-order `AST_Binary` properties to make dump more readable.

closes #769
2017-05-08 06:23:01 +08:00
Alex Lam S.L
4f8ca4626e deprecate low level API (#1877)
fixes #1872
2017-05-08 03:24:42 +08:00
Alex Lam S.L
e54748365c support minify() output as AST (#1878)
- `options.output.ast` (default `false`)
- `options.output.code` (default `true`)
2017-05-08 02:11:45 +08:00
58 changed files with 1091 additions and 573 deletions

View File

@@ -6,15 +6,15 @@
<!-- Note: for ES6 see: https://github.com/mishoo/UglifyJS2/tree/harmony#harmony --> <!-- Note: for ES6 see: https://github.com/mishoo/UglifyJS2/tree/harmony#harmony -->
**`uglify-js` version (`uglifyjs -V`)** **Uglify version (`uglifyjs -V`)**
**JavaScript input - ideally as small as possible.** **JavaScript input** <!-- ideally as small as possible -->
**The `uglifyjs` CLI command executed or `minify()` options used.** **The `uglifyjs` CLI command executed or `minify()` options used.**
**JavaScript output produced and/or the error or warning.** **JavaScript output or error produced.**
<!-- <!--
Note: the release version of uglify-js only supports ES5. Those wishing Note: `uglify-js` only supports ES5.
to minify ES6 should use the experimental harmony branch. Those wishing to minify ES6 should use `uglify-es`.
--> -->

162
README.md
View File

@@ -1,15 +1,12 @@
UglifyJS 3 uglify-es
========== =========
[![Build Status](https://travis-ci.org/mishoo/UglifyJS2.svg)](https://travis-ci.org/mishoo/UglifyJS2) [![Build Status](https://travis-ci.org/mishoo/UglifyJS2.svg)](https://travis-ci.org/mishoo/UglifyJS2)
UglifyJS is a JavaScript parser, minifier, compressor or beautifier toolkit. **uglify-es** is an ECMAScript 2015 parser, minifier, compressor and beautifier toolkit.
#### Note: #### Note:
- **`uglify-js@3.x` has a new API and CLI and is not backwards compatible with [`uglify-js@2.x`](https://github.com/mishoo/UglifyJS2/tree/v2.x)**. - **The `uglify-es` API and CLI is compatible with `uglify-js@3.x`.**
- **Documentation for UglifyJS `2.x` releases can be found [here](https://github.com/mishoo/UglifyJS2/tree/v2.x)**. - **`uglify-es` is not backwards compatible with the `uglify-js@2.x` API and CLI.**
- Release versions of `uglify-js` only support ECMAScript 5 (ES5). If you wish to minify
ES2015+ (ES6+) code then please use the [harmony](#harmony) development branch.
- Node 7 has a known performance regression and runs `uglify-js` twice as slow.
Install Install
------- -------
@@ -19,17 +16,11 @@ First make sure you have installed the latest version of [node.js](http://nodejs
From NPM for use as a command line app: From NPM for use as a command line app:
npm install uglify-js -g npm install uglify-es -g
From NPM for programmatic use: From NPM for programmatic use:
npm install uglify-js npm install uglify-es
From Git:
git clone git://github.com/mishoo/UglifyJS2.git
cd UglifyJS2
npm link .
Usage Usage
----- -----
@@ -95,8 +86,9 @@ The available options are:
`wrap_iife` Wrap IIFEs in parenthesis. Note: you may `wrap_iife` Wrap IIFEs in parenthesis. Note: you may
want to disable `negate_iife` under want to disable `negate_iife` under
compressor options. compressor options.
-o, --output <file> Output file (default STDOUT). Specify "spidermonkey" -o, --output <file> Output file path (default STDOUT). Specify `ast` or
to dump SpiderMonkey AST format (as JSON) to STDOUT. `spidermonkey` to write UglifyJS or SpiderMonkey AST
as JSON to STDOUT respectively.
--comments [filter] Preserve copyright comments in the output. By --comments [filter] Preserve copyright comments in the output. By
default this works like Google Closure, keeping default this works like Google Closure, keeping
JSDoc-style comments that contain "@license" or JSDoc-style comments that contain "@license" or
@@ -154,16 +146,21 @@ debugging your compressed JavaScript. To get a source map, pass
`--source-map --output output.js` (source map will be written out to `--source-map --output output.js` (source map will be written out to
`output.js.map`). `output.js.map`).
Additionally you might need `--source-map root=<URL>` to pass the URL where Additional options:
the original files can be found. Use `--source-map url=<URL>` to specify
the URL where the source map can be found. - `--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: For example:
uglifyjs /home/doe/work/foo/src/js/file1.js \ uglifyjs js/file1.js js/file2.js \
/home/doe/work/foo/src/js/file2.js \
-o foo.min.js -c -m \ -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 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 output in `foo.min.js` and the source map in `foo.min.js.map`. The source
@@ -182,11 +179,9 @@ CoffeeScript → compiled JS, UglifyJS can generate a map from CoffeeScript →
compressed JS by mapping every token in the compiled JS to its original compressed JS by mapping every token in the compiled JS to its original
location. location.
To use this feature you need to pass `--in-source-map To use this feature pass `--source-map content="/path/to/input/source.map"`
/path/to/input/source.map` or `--in-source-map inline` if the source map is or `--source-map content=inline` if the source map is included inline with
included inline with the sources. Normally the input source map should also the sources.
point to the file containing the generated JS, so if that's correct you can
omit input files from the command line.
## Mangler options ## Mangler options
@@ -404,13 +399,19 @@ to set `true`; it's effectively a shortcut for `foo=true`).
compressor from discarding function names. Useful for code relying on compressor from discarding function names. Useful for code relying on
`Function.prototype.name`. See also: the `keep_fnames` [mangle option](#mangle). `Function.prototype.name`. See also: the `keep_fnames` [mangle option](#mangle).
- `passes` -- default `1`. Number of times to run compress. Use an - `passes` -- default `1`. Number of times to run compress with a maximum of 3.
integer argument larger than 1 to further reduce code size in some cases. In some cases more than one pass leads to further compressed code. Keep in
Note: raising the number of passes will increase uglify compress time. mind more passes will take more time.
- `keep_infinity` -- default `false`. Pass `true` to prevent `Infinity` from - `keep_infinity` -- default `false`. Pass `true` to prevent `Infinity` from
being compressed into `1/0`, which may cause performance issues on Chrome. being compressed into `1/0`, which may cause performance issues on Chrome.
- `side_effects` -- default `false`. Pass `true` to potentially drop functions
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()`;
### The `unsafe` option ### The `unsafe` option
It enables some transformations that *might* break code logic in certain It enables some transformations that *might* break code logic in certain
@@ -469,7 +470,7 @@ You can also use conditional compilation via the programmatic API. With the diff
property name is `global_defs` and is a compressor property: property name is `global_defs` and is a compressor property:
```js ```js
uglifyJS.minify([ "input.js"], { uglifyJS.minify(fs.readFileSync("input.js", "utf8"), {
compress: { compress: {
dead_code: true, dead_code: true,
global_defs: { global_defs: {
@@ -489,21 +490,21 @@ can pass additional arguments that control the code output:
Passing `-b` will set this to true, but you might need to pass `-b` even 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 when you want to generate minified code, in order to specify additional
arguments, so you can use `-b beautify=false` to override it. arguments, so you can use `-b beautify=false` to override it.
- `indent-level` (default 4) - `indent_level` (default 4)
- `indent-start` (default 0) -- prefix all lines by that many spaces - `indent_start` (default 0) -- prefix all lines by that many spaces
- `quote-keys` (default `false`) -- pass `true` to quote all keys in literal - `quote_keys` (default `false`) -- pass `true` to quote all keys in literal
objects objects
- `space-colon` (default `true`) -- insert a space after the colon signs - `space_colon` (default `true`) -- insert a space after the colon signs
- `ascii-only` (default `false`) -- escape Unicode characters in strings and - `ascii_only` (default `false`) -- escape Unicode characters in strings and
regexps (affects directives with non-ascii characters becoming invalid) regexps (affects directives with non-ascii characters becoming invalid)
- `inline-script` (default `false`) -- escape the slash in occurrences of - `inline_script` (default `false`) -- escape the slash in occurrences of
`</script` in strings `</script` in strings
- `width` (default 80) -- only takes effect when beautification is on, this - `width` (default 80) -- only takes effect when beautification is on, this
specifies an (orientative) line width that the beautifier will try to specifies an (orientative) line width that the beautifier will try to
obey. It refers to the width of the line text (excluding indentation). 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 It doesn't work very well currently, but it does make the code generated
by UglifyJS more readable. by UglifyJS more readable.
- `max-line-len` (default 32000) -- maximum line length (for uglified code) - `max_line_len` (default 32000) -- maximum line length (for uglified code)
- `bracketize` (default `false`) -- always insert brackets in `if`, `for`, - `bracketize` (default `false`) -- always insert brackets in `if`, `for`,
`do`, `while` or `with` statements, even if their body is a single `do`, `while` or `with` statements, even if their body is a single
statement. statement.
@@ -590,7 +591,7 @@ API Reference
Assuming installation via NPM, you can load UglifyJS in your application Assuming installation via NPM, you can load UglifyJS in your application
like this: like this:
```javascript ```javascript
var UglifyJS = require("uglify-js"); var UglifyJS = require("uglify-es");
``` ```
There is a single toplevel function, `minify(files, options)`, which will There is a single toplevel function, `minify(files, options)`, which will
@@ -598,7 +599,8 @@ performs all the steps in a configurable manner.
Example: Example:
```javascript ```javascript
var result = UglifyJS.minify("var b = function() {};"); var result = UglifyJS.minify("var b = function() {};");
console.log(result.code); // minified output console.log(result.code); // minified output
console.log(result.error); // runtime error
``` ```
You can also compress multiple files: You can also compress multiple files:
@@ -677,61 +679,47 @@ Other options:
##### mangle ##### mangle
- `reserved` - pass an array of identifiers that should be excluded from mangling - `reserved` - pass an array of identifiers that should be excluded from mangling
- `toplevel` — mangle names declared in the toplevel scope (disabled by - `toplevel` — mangle names declared in the toplevel scope (disabled by
default). default).
- `eval` — mangle names visible in scopes where eval or with are used - `eval` — mangle names visible in scopes where eval or with are used
(disabled by default). (disabled by default).
- `keep_fnames` -- default `false`. Pass `true` to not mangle - `keep_fnames` -- default `false`. Pass `true` to not mangle
function names. Useful for code relying on `Function.prototype.name`. function names. Useful for code relying on `Function.prototype.name`.
See also: the `keep_fnames` [compress option](#compressor-options). See also: the `keep_fnames` [compress option](#compressor-options).
Examples: Examples:
```javascript ```javascript
//tst.js // test.js
var globalVar; var globalVar;
function funcName(firstLongName, anotherLongName) function funcName(firstLongName, anotherLongName)
{ {
var myVariable = firstLongName + anotherLongName; var myVariable = firstLongName + anotherLongName;
} }
```
```javascript
var code = fs.readFileSync("test.js", "utf8");
UglifyJS.minify("tst.js").code; UglifyJS.minify(code).code;
// 'function funcName(a,n){}var globalVar;' // 'function funcName(a,n){}var globalVar;'
UglifyJS.minify("tst.js", { mangle: { reserved: ['firstLongName'] } }).code; UglifyJS.minify(code, { mangle: { reserved: ['firstLongName'] } }).code;
// 'function funcName(firstLongName,a){}var globalVar;' // 'function funcName(firstLongName,a){}var globalVar;'
UglifyJS.minify("tst.js", { mangle: { toplevel: true } }).code; UglifyJS.minify(code, { mangle: { toplevel: true } }).code;
// 'function n(n,a){}var a;' // 'function n(n,a){}var a;'
``` ```
##### mangle.properties options ##### mangle.properties options
- `regex` — Pass a RegExp to only mangle certain names - `regex` — Pass a RegExp to only mangle certain names
- `keep_quoted` — Only mangle unquoted property names - `keep_quoted` — Only mangle unquoted property names
- `debug` — Mangle names with the original name still present. Defaults to `false`. - `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. Pass an empty string to enable, or a non-empty string to set the suffix.
[acorn]: https://github.com/ternjs/acorn [acorn]: https://github.com/ternjs/acorn
[sm-spec]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k [sm-spec]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k
#### Harmony
If you wish to use the experimental [harmony](https://github.com/mishoo/UglifyJS2/commits/harmony)
branch to minify ES2015+ (ES6+) code please use the following in your `package.json` file:
```
"uglify-js": "git+https://github.com/mishoo/UglifyJS2.git#harmony"
```
or to directly install the experimental harmony version of uglify:
```
npm install --save-dev uglify-js@github:mishoo/UglifyJS2#harmony
```
See [#448](https://github.com/mishoo/UglifyJS2/issues/448) for additional details.

View File

@@ -15,6 +15,7 @@ var path = require("path");
var program = require("commander"); var program = require("commander");
var UglifyJS = require("../tools/node"); var UglifyJS = require("../tools/node");
var skip_keys = [ "cname", "enclosed", "parent_scope", "scope", "thedef", "uses_eval", "uses_with" ];
var files = {}; var files = {};
var options = { var options = {
compress: false, compress: false,
@@ -89,7 +90,7 @@ if (program.mangleProps) {
if (typeof program.mangleProps != "object") program.mangleProps = {}; if (typeof program.mangleProps != "object") program.mangleProps = {};
if (!Array.isArray(program.mangleProps.reserved)) program.mangleProps.reserved = []; if (!Array.isArray(program.mangleProps.reserved)) program.mangleProps.reserved = [];
require("../tools/domprops").forEach(function(name) { require("../tools/domprops").forEach(function(name) {
UglifyJS.push_uniq(program.mangleProps.reserved, name); UglifyJS._push_uniq(program.mangleProps.reserved, name);
}); });
} }
if (typeof options.mangle != "object") options.mangle = {}; if (typeof options.mangle != "object") options.mangle = {};
@@ -107,6 +108,12 @@ if (program.nameCache) {
} }
} }
} }
if (program.output == "ast") {
options.output = {
ast: true,
code: false
};
}
if (program.parse) { if (program.parse) {
if (program.parse.acorn || program.parse.spidermonkey) { if (program.parse.acorn || program.parse.spidermonkey) {
if (program.sourceMap) fatal("ERROR: inline source map only works with built-in parser"); if (program.sourceMap) fatal("ERROR: inline source map only works with built-in parser");
@@ -165,7 +172,7 @@ function run() {
UglifyJS.AST_Node.warn_function = function(msg) { UglifyJS.AST_Node.warn_function = function(msg) {
console.error("WARN:", msg); console.error("WARN:", msg);
}; };
if (program.stats) program.stats = Date.now(); if (program.stats) program.stats = Date.now();
try { try {
if (program.parse) { if (program.parse) {
if (program.parse.acorn) { if (program.parse.acorn) {
@@ -185,9 +192,13 @@ function run() {
}); });
} }
} }
var result = UglifyJS.minify(files, options);
} catch (ex) { } catch (ex) {
if (ex instanceof UglifyJS.JS_Parse_Error) { fatal(ex);
}
var result = UglifyJS.minify(files, options);
if (result.error) {
var ex = result.error;
if (ex.name == "SyntaxError") {
console.error("Parse error at " + ex.filename + ":" + ex.line + "," + ex.col); console.error("Parse error at " + ex.filename + ":" + ex.line + "," + ex.col);
var col = ex.col; var col = ex.col;
var lines = files[ex.filename].split(/\r?\n/); var lines = files[ex.filename].split(/\r?\n/);
@@ -209,10 +220,32 @@ function run() {
console.error("Supported options:"); console.error("Supported options:");
console.error(ex.defs); console.error(ex.defs);
} }
fatal("ERROR: " + ex.message); fatal(ex);
} } else if (program.output == "ast") {
if (program.output == "spidermonkey") { console.log(JSON.stringify(result.ast, function(key, value) {
console.log(JSON.stringify(UglifyJS.parse(new Buffer(result.code).toString()).to_mozilla_ast(), null, 2)); if (skip_key(key)) return;
if (value instanceof UglifyJS.AST_Token) return;
if (value instanceof UglifyJS.Dictionary) return;
if (value instanceof UglifyJS.AST_Node) {
var result = {
_class: "AST_" + value.TYPE
};
value.CTOR.PROPS.forEach(function(prop) {
result[prop] = value[prop];
});
return result;
}
return value;
}, 2));
} else if (program.output == "spidermonkey") {
console.log(JSON.stringify(UglifyJS.minify(result.code, {
compress: false,
mangle: false,
output: {
ast: true,
code: false
}
}).ast.to_mozilla_ast(), null, 2));
} else if (program.output) { } else if (program.output) {
fs.writeFileSync(program.output, result.code); fs.writeFileSync(program.output, result.code);
if (result.map) { if (result.map) {
@@ -230,8 +263,9 @@ function run() {
} }
function fatal(message) { function fatal(message) {
console.error(message); if (message instanceof Error) message = message.stack.replace(/^\S*?Error:/, "ERROR:")
process.exit(1); console.error(message);
process.exit(1);
} }
// A file glob function that only supports "*" and "?" wildcards in the basename. // A file glob function that only supports "*" and "?" wildcards in the basename.
@@ -270,7 +304,7 @@ function read_file(path, default_value) {
return fs.readFileSync(path, "utf8"); return fs.readFileSync(path, "utf8");
} catch (ex) { } catch (ex) {
if (ex.code == "ENOENT" && default_value != null) return default_value; if (ex.code == "ENOENT" && default_value != null) return default_value;
fatal("ERROR: " + ex.message); fatal(ex);
} }
} }
@@ -278,9 +312,17 @@ function parse_js(flag, constants) {
return function(value, options) { return function(value, options) {
options = options || {}; options = options || {};
try { try {
UglifyJS.parse(value, { UglifyJS.minify(value, {
expression: true parse: {
}).walk(new UglifyJS.TreeWalker(function(node) { expression: true
},
compress: false,
mangle: false,
output: {
ast: true,
code: false
}
}).ast.walk(new UglifyJS.TreeWalker(function(node) {
if (node instanceof UglifyJS.AST_Assign) { if (node instanceof UglifyJS.AST_Assign) {
var name = node.left.print_to_string(); var name = node.left.print_to_string();
var value = node.right; var value = node.right;
@@ -337,3 +379,7 @@ function to_cache(key) {
} }
return cache[key]; return cache[key];
} }
function skip_key(key) {
return skip_keys.indexOf(key) >= 0;
}

View File

@@ -857,7 +857,7 @@ var AST_UnaryPostfix = DEFNODE("UnaryPostfix", null, {
$documentation: "Unary postfix expression, i.e. `i++`" $documentation: "Unary postfix expression, i.e. `i++`"
}, AST_Unary); }, AST_Unary);
var AST_Binary = DEFNODE("Binary", "left operator right", { var AST_Binary = DEFNODE("Binary", "operator left right", {
$documentation: "Binary expression, i.e. `a + b`", $documentation: "Binary expression, i.e. `a + b`",
$propdoc: { $propdoc: {
left: "[AST_Node] left-hand side expression", left: "[AST_Node] left-hand side expression",
@@ -932,7 +932,7 @@ var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", {
$documentation: "Base class for literal object properties", $documentation: "Base class for literal object properties",
$propdoc: { $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", 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) { _walk: function(visitor) {
return visitor._visit(this, function(){ return visitor._visit(this, function(){
@@ -1018,10 +1018,6 @@ var AST_NewTarget = DEFNODE("NewTarget", null, {
$documentation: "A reference to new.target" $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", { var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", "init", {
$documentation: "A declaration symbol (symbol in var/const, function name or argument, symbol in catch)", $documentation: "A declaration symbol (symbol in var/const, function name or argument, symbol in catch)",
}, AST_Symbol); }, AST_Symbol);

View File

@@ -361,16 +361,27 @@ merge(Compressor.prototype, {
// So existing transformation rules can work on them. // So existing transformation rules can work on them.
node.argnames.forEach(function(arg, i) { node.argnames.forEach(function(arg, i) {
var d = arg.definition(); var d = arg.definition();
d.fixed = function() { if (!node.uses_arguments && d.fixed === undefined) {
return iife.args[i] || make_node(AST_Undefined, iife); d.fixed = function() {
}; return iife.args[i] || make_node(AST_Undefined, iife);
mark(d, true); };
mark(d, true);
} else {
d.fixed = false;
}
}); });
} }
descend(); descend();
pop(); pop();
return true; 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 if (node instanceof AST_Binary
&& (node.operator == "&&" || node.operator == "||")) { && (node.operator == "&&" || node.operator == "||")) {
node.left.walk(tw); node.left.walk(tw);
@@ -498,7 +509,9 @@ merge(Compressor.prototype, {
function reset_def(def) { function reset_def(def) {
def.escaped = false; 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; def.fixed = undefined;
} else { } else {
def.fixed = false; def.fixed = false;
@@ -532,6 +545,14 @@ merge(Compressor.prototype, {
return lhs instanceof AST_SymbolRef && lhs.definition().orig[0] instanceof AST_SymbolLambda; return lhs instanceof AST_SymbolRef && lhs.definition().orig[0] instanceof AST_SymbolLambda;
} }
function is_reference_const(ref) {
if (!(ref instanceof AST_SymbolRef)) return false;
var orig = ref.definition().orig;
for (var i = orig.length; --i >= 0;) {
if (orig[i] instanceof AST_SymbolConst) return true;
}
}
function find_variable(compressor, name) { function find_variable(compressor, name) {
var scope, i = 0; var scope, i = 0;
while (scope = compressor.parent(i++)) { while (scope = compressor.parent(i++)) {
@@ -799,14 +820,15 @@ merge(Compressor.prototype, {
} }
function get_lhs(expr) { function get_lhs(expr) {
if (expr instanceof AST_VarDef) { if (expr instanceof AST_VarDef && expr.name instanceof AST_SymbolDeclaration) {
var def = expr.name.definition(); var def = expr.name.definition();
if (def.orig.length > 1 if (def.orig.length > 1
|| def.references.length == 1 && (!def.global || compressor.toplevel(def))) { || def.references.length == 1 && (!def.global || compressor.toplevel(def))) {
return make_node(AST_SymbolRef, expr.name, expr.name); return make_node(AST_SymbolRef, expr.name, expr.name);
} }
} else { } else {
return expr[expr instanceof AST_Assign ? "left" : "expression"]; var lhs = expr[expr instanceof AST_Assign ? "left" : "expression"];
return !is_reference_const(lhs) && lhs;
} }
} }
@@ -1231,12 +1253,12 @@ merge(Compressor.prototype, {
&& !node.expression.has_side_effects(compressor); && !node.expression.has_side_effects(compressor);
} }
// may_eq_null() // may_throw_on_access()
// returns true if this node may evaluate to null or undefined // returns true if this node may be null, undefined or contain `AST_Accessor`
(function(def) { (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"); 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) { function is_strict(pure_getters) {
@@ -1248,7 +1270,12 @@ merge(Compressor.prototype, {
def(AST_Undefined, return_true); def(AST_Undefined, return_true);
def(AST_Constant, return_false); def(AST_Constant, return_false);
def(AST_Array, 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_Function, return_false);
def(AST_UnaryPostfix, return_false); def(AST_UnaryPostfix, return_false);
def(AST_UnaryPrefix, function() { def(AST_UnaryPrefix, function() {
@@ -1257,33 +1284,33 @@ merge(Compressor.prototype, {
def(AST_Binary, function(pure_getters) { def(AST_Binary, function(pure_getters) {
switch (this.operator) { switch (this.operator) {
case "&&": case "&&":
return this.left._eq_null(pure_getters); return this.left._throw_on_access(pure_getters);
case "||": case "||":
return this.left._eq_null(pure_getters) return this.left._throw_on_access(pure_getters)
&& this.right._eq_null(pure_getters); && this.right._throw_on_access(pure_getters);
default: default:
return false; return false;
} }
}) })
def(AST_Assign, function(pure_getters) { def(AST_Assign, function(pure_getters) {
return this.operator == "=" return this.operator == "="
&& this.right._eq_null(pure_getters); && this.right._throw_on_access(pure_getters);
}) })
def(AST_Conditional, function(pure_getters) { def(AST_Conditional, function(pure_getters) {
return this.consequent._eq_null(pure_getters) return this.consequent._throw_on_access(pure_getters)
|| this.alternative._eq_null(pure_getters); || this.alternative._throw_on_access(pure_getters);
}) })
def(AST_Sequence, function(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) { def(AST_SymbolRef, function(pure_getters) {
if (this.is_undefined) return true; if (this.is_undefined) return true;
if (!is_strict(pure_getters)) return false; if (!is_strict(pure_getters)) return false;
var fixed = this.fixed_value(); var fixed = this.fixed_value();
return !fixed || fixed._eq_null(pure_getters); return !fixed || fixed._throw_on_access(pure_getters);
}); });
})(function(node, func) { })(function(node, func) {
node.DEFMETHOD("_eq_null", func); node.DEFMETHOD("_throw_on_access", func);
}); });
/* -----[ boolean/negation helpers ]----- */ /* -----[ boolean/negation helpers ]----- */
@@ -1844,11 +1871,11 @@ merge(Compressor.prototype, {
return any(this.elements, compressor); return any(this.elements, compressor);
}); });
def(AST_Dot, function(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); || this.expression.has_side_effects(compressor);
}); });
def(AST_Sub, function(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.expression.has_side_effects(compressor)
|| this.property.has_side_effects(compressor); || this.property.has_side_effects(compressor);
}); });
@@ -2029,6 +2056,7 @@ merge(Compressor.prototype, {
&& node instanceof AST_Assign && node instanceof AST_Assign
&& node.operator == "=" && node.operator == "="
&& node.left instanceof AST_SymbolRef && node.left instanceof AST_SymbolRef
&& !is_reference_const(node.left)
&& scope === self) { && scope === self) {
node.right.walk(tw); node.right.walk(tw);
return true; return true;
@@ -2482,6 +2510,7 @@ merge(Compressor.prototype, {
var args = trim(this.args, compressor, first_in_statement); var args = trim(this.args, compressor, first_in_statement);
return args && make_sequence(this, args); return args && make_sequence(this, args);
}); });
def(AST_Accessor, return_null);
def(AST_Function, return_null); def(AST_Function, return_null);
def(AST_Binary, function(compressor, first_in_statement){ def(AST_Binary, function(compressor, first_in_statement){
var right = this.right.drop_side_effect_free(compressor); var right = this.right.drop_side_effect_free(compressor);
@@ -2549,11 +2578,11 @@ merge(Compressor.prototype, {
return values && make_sequence(this, values); return values && make_sequence(this, values);
}); });
def(AST_Dot, function(compressor, first_in_statement){ 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); return this.expression.drop_side_effect_free(compressor, first_in_statement);
}); });
def(AST_Sub, function(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); 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); if (!expression) return this.property.drop_side_effect_free(compressor, first_in_statement);
var property = this.property.drop_side_effect_free(compressor); var property = this.property.drop_side_effect_free(compressor);
@@ -3314,7 +3343,7 @@ merge(Compressor.prototype, {
&& (left.operator == "++" || left.operator == "--")) { && (left.operator == "++" || left.operator == "--")) {
left = left.expression; left = left.expression;
} else left = null; } else left = null;
if (!left || is_lhs_read_only(left)) { if (!left || is_lhs_read_only(left) || is_reference_const(left)) {
expressions[++i] = cdr; expressions[++i] = cdr;
continue; continue;
} }

View File

@@ -54,9 +54,11 @@ function minify(files, options) {
cache: null, cache: null,
eval: false, eval: false,
ie8: false, ie8: false,
keep_classnames: false,
keep_fnames: false, keep_fnames: false,
properties: false, properties: false,
reserved: [], reserved: [],
safari10: false,
toplevel: false, toplevel: false,
}, true); }, true);
} }
@@ -108,38 +110,46 @@ function minify(files, options) {
toplevel = mangle_properties(toplevel, options.mangle.properties); toplevel = mangle_properties(toplevel, options.mangle.properties);
} }
} }
if (options.sourceMap) { var result = {};
if (typeof options.sourceMap.content == "string") { if (options.output.ast) {
options.sourceMap.content = JSON.parse(options.sourceMap.content); result.ast = toplevel;
} }
options.output.source_map = SourceMap({ if (!HOP(options.output, "code") || options.output.code) {
file: options.sourceMap.filename, if (options.sourceMap) {
orig: options.sourceMap.content, if (typeof options.sourceMap.content == "string") {
root: options.sourceMap.root options.sourceMap.content = JSON.parse(options.sourceMap.content);
}); }
if (options.sourceMap.includeSources) { options.output.source_map = SourceMap({
for (var name in files) { file: options.sourceMap.filename,
options.output.source_map.get().setSourceContent(name, files[name]); orig: options.sourceMap.content,
root: options.sourceMap.root
});
if (options.sourceMap.includeSources) {
for (var name in files) {
options.output.source_map.get().setSourceContent(name, files[name]);
}
} }
} }
} delete options.output.ast;
var stream = OutputStream(options.output); delete options.output.code;
toplevel.print(stream); var stream = OutputStream(options.output);
var result = { toplevel.print(stream);
code: stream.get() result.code = stream.get();
}; if (options.sourceMap) {
if (options.sourceMap) { result.map = options.output.source_map.toString();
result.map = options.output.source_map.toString(); if (options.sourceMap.url == "inline") {
if (options.sourceMap.url == "inline") { result.code += "\n//# sourceMappingURL=data:application/json;charset=utf-8;base64," + to_base64(result.map);
result.code += "\n//# sourceMappingURL=data:application/json;charset=utf-8;base64," + to_base64(result.map); } else if (options.sourceMap.url) {
} else if (options.sourceMap.url) { result.code += "\n//# sourceMappingURL=" + options.sourceMap.url;
result.code += "\n//# sourceMappingURL=" + options.sourceMap.url; }
} }
} }
if (warnings.length) { if (warnings.length) {
result.warnings = warnings; result.warnings = warnings;
} }
return result; return result;
} catch (ex) {
return { error: ex };
} finally { } finally {
AST_Node.warn_function = warn_function; AST_Node.warn_function = warn_function;
} }

View File

@@ -111,23 +111,19 @@
}, },
Property: function(M) { Property: function(M) {
var key = M.key; var key = M.key;
var name = key.type == "Identifier" ? key.name : key.value;
var args = { var args = {
start : my_start_token(key), start : my_start_token(key),
end : my_end_token(M.value), end : my_end_token(M.value),
key : name, key : key.type == "Identifier" ? key.name : key.value,
value : from_moz(M.value) value : from_moz(M.value)
}; };
switch (M.kind) { if (M.kind == "init") return new AST_ObjectKeyVal(args);
case "init": args.key = new AST_SymbolMethod({
return new AST_ObjectKeyVal(args); name: args.key
case "set": });
args.value.name = from_moz(key); args.value = new AST_Accessor(args.value);
return new AST_ObjectSetter(args); if (M.kind == "get") return new AST_ObjectGetter(args);
case "get": if (M.kind == "set") return new AST_ObjectSetter(args);
args.value.name = from_moz(key);
return new AST_ObjectGetter(args);
}
}, },
ArrayExpression: function(M) { ArrayExpression: function(M) {
return new AST_Array({ return new AST_Array({
@@ -260,10 +256,7 @@
map("CallExpression", AST_Call, "callee>expression, arguments@args"); map("CallExpression", AST_Call, "callee>expression, arguments@args");
def_to_moz(AST_Toplevel, function To_Moz_Program(M) { def_to_moz(AST_Toplevel, function To_Moz_Program(M) {
return { return to_moz_scope("Program", M);
type: "Program",
body: M.body.map(to_moz)
};
}); });
def_to_moz(AST_Defun, function To_Moz_FunctionDeclaration(M) { def_to_moz(AST_Defun, function To_Moz_FunctionDeclaration(M) {
@@ -271,7 +264,7 @@
type: "FunctionDeclaration", type: "FunctionDeclaration",
id: to_moz(M.name), id: to_moz(M.name),
params: M.argnames.map(to_moz), params: M.argnames.map(to_moz),
body: to_moz_block(M) body: to_moz_scope("BlockStatement", M)
} }
}); });
@@ -280,7 +273,7 @@
type: "FunctionExpression", type: "FunctionExpression",
id: to_moz(M.name), id: to_moz(M.name),
params: M.argnames.map(to_moz), 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) { def_to_moz(AST_ObjectProperty, function To_Moz_Property(M) {
var key = ( var key = {
is_identifier(M.key) type: "Literal",
? {type: "Identifier", name: M.key} value: M.key instanceof AST_SymbolMethod ? M.key.name : M.key
: {type: "Literal", value: M.key} };
);
var kind; var kind;
if (M instanceof AST_ObjectKeyVal) { if (M instanceof AST_ObjectKeyVal) {
kind = "init"; kind = "init";
@@ -551,8 +543,8 @@
moz_to_me = new Function("U2", "my_start_token", "my_end_token", "from_moz", "return(" + moz_to_me + ")")( 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 exports, my_start_token, my_end_token, from_moz
); );
me_to_moz = new Function("to_moz", "to_moz_block", "return(" + me_to_moz + ")")( me_to_moz = new Function("to_moz", "to_moz_block", "to_moz_scope", "return(" + me_to_moz + ")")(
to_moz, to_moz_block to_moz, to_moz_block, to_moz_scope
); );
MOZ_TO_ME[moztype] = moz_to_me; MOZ_TO_ME[moztype] = moz_to_me;
def_to_moz(mytype, me_to_moz); 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
};
};
})(); })();

View File

@@ -215,16 +215,6 @@ function OutputStream(options) {
var might_add_newline = 0; var might_add_newline = 0;
var last = ""; var last = "";
function last_char() {
var char = last.charAt(last.length - 1);
if (is_surrogate_pair_tail(char)) {
return last.charAt(last.length - 2) + char;
}
return char;
};
var ensure_line_len = options.max_line_len ? function() { var ensure_line_len = options.max_line_len ? function() {
if (current_col > options.max_line_len) { if (current_col > options.max_line_len) {
if (might_add_newline) { if (might_add_newline) {
@@ -247,7 +237,7 @@ function OutputStream(options) {
function print(str) { function print(str) {
str = String(str); str = String(str);
var ch = get_full_char(str, 0); var ch = get_full_char(str, 0);
var prev = last_char(); var prev = get_full_char(last, last.length - 1);
if (might_need_semicolon) { if (might_need_semicolon) {
might_need_semicolon = false; might_need_semicolon = false;

View File

@@ -134,8 +134,17 @@ var UNICODE = {
function get_full_char(str, pos) { function get_full_char(str, pos) {
var char = str.charAt(pos); var char = str.charAt(pos);
if (char >= "\ud800" && char <= "\udbff") { if (is_surrogate_pair_head(char)) {
return char + str.charAt(pos + 1); var next = str.charAt(pos + 1);
if (is_surrogate_pair_tail(next)) {
return char + next;
}
}
if (is_surrogate_pair_tail(char)) {
var prev = str.charAt(pos - 1);
if (is_surrogate_pair_head(prev)) {
return prev + char;
}
} }
return char; return char;
} }
@@ -152,8 +161,8 @@ function get_full_char_length(str) {
var surrogates = 0; var surrogates = 0;
for (var i = 0; i < str.length; i++) { for (var i = 0; i < str.length; i++) {
if (str.charCodeAt(i) >= 0xd800 && str.charCodeAt(i) <= 0xdbff) { if (is_surrogate_pair_head(str.charCodeAt(i))) {
if (str.charCodeAt(i + 1) >= 0xdc00 && str.charCodeAt(i + 1) <= 0xdfff) { if (is_surrogate_pair_tail(str.charCodeAt(i + 1))) {
surrogates++; surrogates++;
i++; i++;
} }
@@ -291,7 +300,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
ch = "\n"; ch = "\n";
} }
} else { } else {
if (is_surrogate_pair_head(ch)) { if (ch.length > 1) {
++S.pos; ++S.pos;
++S.col; ++S.col;
} }
@@ -967,24 +976,20 @@ function parse($TEXT, options) {
handle_regexp(); handle_regexp();
switch (S.token.type) { switch (S.token.type) {
case "string": case "string":
var dir = false; if (S.in_directives) {
if (S.in_directives === true) { tmp = peek();
if ((is_token(peek(), "punc", ";") || peek().nlb) && S.token.raw.indexOf("\\") === -1) { if (S.token.raw.indexOf("\\") == -1
&& (tmp.nlb
|| is_token(tmp, "eof")
|| is_token(tmp, "punc", ";")
|| is_token(tmp, "punc", "}"))) {
S.input.add_directive(S.token.value); S.input.add_directive(S.token.value);
} else { } else {
S.in_directives = false; S.in_directives = false;
} }
} }
var dir = S.in_directives, stat = simple_statement(); var dir = S.in_directives, stat = simple_statement();
if (dir) { return dir ? new AST_Directive(stat.body) : stat;
return new AST_Directive({
start : stat.body.start,
end : stat.body.end,
quote : stat.body.quote,
value : stat.body.value,
});
}
return stat;
case "template_head": case "template_head":
case "num": case "num":
case "regexp": case "regexp":
@@ -1776,7 +1781,7 @@ function parse($TEXT, options) {
name : as_symbol(sym_type), name : as_symbol(sym_type),
value : is("operator", "=") value : is("operator", "=")
? (next(), expression(false, no_in)) ? (next(), expression(false, no_in))
: kind === "const" && S.input.has_directive("use strict") : !no_in && kind === "const" && S.input.has_directive("use strict")
? croak("Missing initializer in const declaration") : null, ? croak("Missing initializer in const declaration") : null,
end : prev() end : prev()
}) })
@@ -1805,10 +1810,10 @@ function parse($TEXT, options) {
}); });
}; };
var const_ = function() { var const_ = function(no_in) {
return new AST_Const({ return new AST_Const({
start : prev(), start : prev(),
definitions : vardefs(false, "const"), definitions : vardefs(no_in, "const"),
end : prev() end : prev()
}); });
}; };
@@ -2293,39 +2298,39 @@ function parse($TEXT, options) {
if (is("keyword", "default")) { if (is("keyword", "default")) {
is_default = true; is_default = true;
next(); next();
} } else {
exported_names = import_names(false);
exported_names = import_names(false); if (exported_names) {
if (is("name", "from")) {
next();
if (exported_names) { var mod_str = S.token;
if (is("name", "from")) { if (mod_str.type !== 'string') {
next(); unexpected();
}
next();
var mod_str = S.token; return new AST_Export({
if (mod_str.type !== 'string') { start: start,
unexpected(); 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(),
});
} }
} }

View File

@@ -48,9 +48,10 @@ function find_builtins(reserved) {
// Compatibility fix for some standard defined globals not defined on every js environment // 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 new_globals = ["Symbol", "Map", "Promise", "Proxy", "Reflect", "Set", "WeakMap", "WeakSet"];
var objects = {}; var objects = {};
var global_ref = typeof global === "object" ? global : self;
new_globals.forEach(function (new_global) { 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 // NaN will be included due to Number.NaN

View File

@@ -102,6 +102,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
options = defaults(options, { options = defaults(options, {
cache: null, cache: null,
ie8: false, ie8: false,
safari10: false,
}); });
// pass 1: setup scope chaining and handle definitions // 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_destructuring = null;
var in_export = false; var in_export = false;
var in_block = 0; var in_block = 0;
var for_scopes = [];
var tw = new TreeWalker(function(node, descend){ var tw = new TreeWalker(function(node, descend){
if (node.is_block_scope()) { if (node.is_block_scope()) {
var save_scope = 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.uses_eval = save_scope.uses_eval;
scope.directives = save_scope.directives; scope.directives = save_scope.directives;
} }
if (options.safari10) {
if (node instanceof AST_For || node instanceof AST_ForIn) {
for_scopes.push(scope);
}
}
descend(); descend();
scope = save_scope; scope = save_scope;
return true; 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) { if (options.cache) {
this.cname = options.cache.cname; this.cname = options.cache.cname;
} }
@@ -453,11 +473,6 @@ AST_Symbol.DEFMETHOD("unmangleable", function(options){
return def && def.unmangleable(options); return def && def.unmangleable(options);
}); });
// property accessors are not mangleable
AST_SymbolAccessor.DEFMETHOD("unmangleable", function(){
return true;
});
// labels are always mangleable // labels are always mangleable
AST_Label.DEFMETHOD("unmangleable", function(){ AST_Label.DEFMETHOD("unmangleable", function(){
return false; return false;

View File

@@ -239,6 +239,10 @@ TreeTransformer.prototype = new TreeWalker;
self.expression = self.expression.transform(tw); 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) { _(AST_TemplateString, function(self, tw) {
for (var i = 0; i < self.segments.length; i++) { for (var i = 0; i < self.segments.length; i++) {
if (!(self.segments[i] instanceof AST_TemplateSegment)) { if (!(self.segments[i] instanceof AST_TemplateSegment)) {

View File

@@ -1,10 +1,10 @@
{ {
"name": "uglify-js", "name": "uglify-es",
"description": "JavaScript parser, mangler/compressor and beautifier toolkit", "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/)", "author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"version": "3.0.0", "version": "3.0.5",
"engines": { "engines": {
"node": ">=0.8.0" "node": ">=0.8.0"
}, },
@@ -33,20 +33,9 @@
"source-map": "~0.5.1" "source-map": "~0.5.1"
}, },
"devDependencies": { "devDependencies": {
"acorn": "~0.6.0", "acorn": "~5.0.3",
"escodegen": "~1.3.3",
"esfuzz": "~0.3.1",
"estraverse": "~1.5.1",
"mocha": "~2.3.4" "mocha": "~2.3.4"
}, },
"optionalDependencies": {
"uglify-to-browserify": "~1.0.0"
},
"browserify": {
"transform": [
"uglify-to-browserify"
]
},
"scripts": { "scripts": {
"test": "node test/run-tests.js" "test": "node test/run-tests.js"
}, },

View File

@@ -2284,3 +2284,49 @@ compound_assignment: {
} }
expect_stdout: "4" 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
}

View File

@@ -278,3 +278,19 @@ try_catch_finally: {
"1", "1",
] ]
} }
accessor: {
options = {
side_effects: true,
}
input: {
({
get a() {},
set a(v){
this.b = 2;
},
b: 1
});
}
expect: {}
}

View File

@@ -315,3 +315,18 @@ unused: {
console.log(a); console.log(a);
} }
} }
issue_1886: {
options = {
collapse_vars: true,
}
input: {
let [a] = [1];
console.log(a);
}
expect: {
let [a] = [1];
console.log(a);
}
expect_exact: "1"
}

View File

@@ -1228,3 +1228,28 @@ 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
}

View File

@@ -203,15 +203,42 @@ import_all_statement: {
} }
export_statement: { export_statement: {
options = {
evaluate: true,
}
input: { input: {
export default 1; export default 1 + 2;
export var foo = 4; export var foo = 4;
export let foo = 6; export let foo = 6;
export const foo = 6; export const foo = 6;
export function foo() {}; export function foo() {};
export class 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_module_statement: { export_module_statement: {
@@ -392,3 +419,93 @@ format_methods: {
"}", "}",
] ]
} }
issue_1898: {
options = {
}
mangle = {
}
input: {
class Foo {
bar() {
for (const x of [ 6, 5 ]) {
for (let y of [ 4, 3 ]) {
for (var z of [ 2, 1 ]) {
console.log(x, y, z);
}
}
}
}
}
new Foo().bar();
}
expect: {
class Foo {
bar() {
for (const n of [ 6, 5 ])
for (let r of [ 4, 3 ])
for (var o of [ 2, 1 ])
console.log(n, r, o);
}
}
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: []
});
}
}
}
}

View File

@@ -119,3 +119,62 @@ chained: {
a.b.c; 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: {}
}

View File

@@ -41,20 +41,20 @@ reduce_vars: {
var A = 1; var A = 1;
(function() { (function() {
console.log(-3); console.log(-3);
console.log(-4); console.log(A - 5);
})(); })();
(function f1() { (function f1() {
var a = 2; var a = 2;
console.log(-3); console.log(a - 5);
eval("console.log(a);"); eval("console.log(a);");
})(); })();
(function f2(eval) { (function f2(eval) {
var a = 2; var a = 2;
console.log(-3); console.log(a - 5);
eval("console.log(a);"); eval("console.log(a);");
})(eval); })(eval);
"yes"; "yes";
console.log(2); console.log(A + 1);
} }
expect_stdout: true expect_stdout: true
} }
@@ -1749,7 +1749,10 @@ redefine_arguments_3: {
console.log(function() { console.log(function() {
var arguments; var arguments;
return typeof arguments; return typeof arguments;
}(), "number", "undefined"); }(), "number", function(x) {
var arguments = x;
return typeof arguments;
}());
} }
expect_stdout: "object number undefined" expect_stdout: "object number undefined"
} }
@@ -2461,3 +2464,76 @@ issue_1865: {
} }
expect_stdout: true 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"
}

View File

@@ -710,3 +710,27 @@ issue_27: {
})(jQuery); })(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
}

13
test/exports.js Normal file
View File

@@ -0,0 +1,13 @@
exports["Compressor"] = Compressor;
exports["JS_Parse_Error"] = JS_Parse_Error;
exports["OutputStream"] = OutputStream;
exports["SourceMap"] = SourceMap;
exports["TreeWalker"] = TreeWalker;
exports["base54"] = base54;
exports["defaults"] = defaults;
exports["mangle_properties"] = mangle_properties;
exports["minify"] = minify;
exports["parse"] = parse;
exports["string_template"] = string_template;
exports["tokenizer"] = tokenizer;
exports["is_identifier"] = is_identifier;

View File

@@ -1,4 +1,4 @@
var UglifyJS = require('../../'); var UglifyJS = require("../node");
var assert = require("assert"); var assert = require("assert");
describe("Accessor tokens", function() { describe("Accessor tokens", function() {

View File

@@ -1,4 +1,4 @@
var UglifyJS = require('../../'); var UglifyJS = require("../node");
var assert = require("assert"); var assert = require("assert");
describe("arguments", function() { describe("arguments", function() {

View File

@@ -1,5 +1,5 @@
var assert = require("assert"); var assert = require("assert");
var uglify = require("../../"); var uglify = require("../node");
describe("Arrow functions", function() { describe("Arrow functions", function() {
it("Should not accept spread tokens on non-last parameters or without arguments parentheses", function() { it("Should not accept spread tokens on non-last parameters or without arguments parentheses", function() {

View File

@@ -1,5 +1,5 @@
var assert = require("assert"); var assert = require("assert");
var uglify = require("../../"); var uglify = require("../node");
describe("Class", function() { describe("Class", function() {
it("Should not accept spread on non-last parameters in methods", function() { it("Should not accept spread on non-last parameters in methods", function() {

View File

@@ -19,7 +19,7 @@ describe("bin/uglifyjs", function () {
eval(stdout); eval(stdout);
assert.strictEqual(typeof WrappedUglifyJS, 'object'); assert.strictEqual(typeof WrappedUglifyJS, 'object');
assert.strictEqual(true, WrappedUglifyJS.parse('foo;') instanceof WrappedUglifyJS.AST_Node); assert.strictEqual(WrappedUglifyJS.minify("foo([true,,2+3]);").code, "foo([!0,,5]);");
done(); done();
}); });
@@ -185,7 +185,7 @@ describe("bin/uglifyjs", function () {
exec(command, function (err, stdout, stderr) { exec(command, function (err, stdout, stderr) {
assert.ok(err); assert.ok(err);
assert.strictEqual(stderr, "ERROR: inline source map only works with singular input\n"); assert.strictEqual(stderr.split(/\n/)[0], "ERROR: inline source map only works with singular input");
done(); done();
}); });
}); });
@@ -509,4 +509,15 @@ describe("bin/uglifyjs", function () {
return JSON.stringify(map).replace(/"/g, '\\"'); return JSON.stringify(map).replace(/"/g, '\\"');
} }
}); });
it("Should dump AST as JSON", function(done) {
var command = uglifyjscmd + " test/input/global_defs/simple.js -mco ast";
exec(command, function (err, stdout) {
if (err) throw err;
var ast = JSON.parse(stdout);
assert.strictEqual(ast._class, "AST_Toplevel");
assert.ok(Array.isArray(ast.body));
done();
});
});
}); });

View File

@@ -1,4 +1,4 @@
var UglifyJS = require('../../'); var UglifyJS = require("../node");
var assert = require("assert"); var assert = require("assert");
describe("comment filters", function() { describe("comment filters", function() {

View File

@@ -1,5 +1,5 @@
var assert = require("assert"); var assert = require("assert");
var uglify = require("../../"); var uglify = require("../node");
describe("Comment", function() { describe("Comment", function() {
it("Should recognize eol of single line comments", function() { it("Should recognize eol of single line comments", function() {

View File

@@ -1,5 +1,5 @@
var assert = require("assert"); var assert = require("assert");
var uglify = require("../../"); var uglify = require("../node");
describe("Destructuring", function() { describe("Destructuring", function() {
it("Should generate similar trees for destructuring in left hand side expressions, definitions, functions and arrow functions", function() { it("Should generate similar trees for destructuring in left hand side expressions, definitions, functions and arrow functions", function() {

View File

@@ -1,5 +1,5 @@
var assert = require("assert"); var assert = require("assert");
var uglify = require("../../"); var uglify = require("../node");
describe("Directives", function() { describe("Directives", function() {
it ("Should allow tokenizer to store directives state", function() { it ("Should allow tokenizer to store directives state", function() {
@@ -361,18 +361,28 @@ describe("Directives", function() {
var tests = [ var tests = [
[ [
'"use strict";"use strict";"use strict";"use foo";"use strict";;"use sloppy";doSomething("foo");', '"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 // Nothing gets optimised in the compressor because "use asm" is the first statement
'"use asm";"use\\x20strict";1+1;', '"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++) { for (var i = 0; i < tests.length; i++) {
assert.strictEqual( 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][1],
tests[i][0] tests[i][0]
); );

View File

@@ -1,5 +1,5 @@
var assert = require("assert"); var assert = require("assert");
var uglify = require("../../"); var uglify = require("../node");
describe("EOF", function() { describe("EOF", function() {
it("Should test code for at least throwing syntax error when incomplete", function() { it("Should test code for at least throwing syntax error when incomplete", function() {

View File

@@ -1,5 +1,5 @@
var assert = require("assert"); var assert = require("assert");
var uglify = require("../../"); var uglify = require("../node");
describe("Export", function() { describe("Export", function() {
it ("Should parse export directives", function() { it ("Should parse export directives", function() {

View File

@@ -1,5 +1,5 @@
var assert = require("assert"); var assert = require("assert");
var uglify = require("../../"); var uglify = require("../node");
describe("Expression", function() { describe("Expression", function() {
it("Should not allow the first exponentiation operator to be prefixed with an unary operator", function() { it("Should not allow the first exponentiation operator to be prefixed with an unary operator", function() {

View File

@@ -1,5 +1,5 @@
var assert = require("assert"); var assert = require("assert");
var uglify = require("../../"); var uglify = require("../node");
describe("Function", function() { describe("Function", function() {
it ("Should parse binding patterns correctly", function() { it ("Should parse binding patterns correctly", function() {

View File

@@ -1,4 +1,4 @@
var UglifyJS = require('../../'); var UglifyJS = require("../node");
var assert = require("assert"); var assert = require("assert");
describe("Getters and setters", function() { describe("Getters and setters", function() {

View File

@@ -1,5 +1,5 @@
var assert = require("assert"); var assert = require("assert");
var uglify = require("../../"); var uglify = require("../node");
describe("Left-hand side expressions", function () { describe("Left-hand side expressions", function () {
it("Should parse destructuring with const/let/var correctly", function () { it("Should parse destructuring with const/let/var correctly", function () {

View File

@@ -1,4 +1,4 @@
var Uglify = require('../../'); var Uglify = require("../node");
var assert = require("assert"); var assert = require("assert");
describe("line-endings", function() { describe("line-endings", function() {

View File

@@ -114,17 +114,18 @@ describe("minify", function() {
} }
}); });
it("Should fail with multiple input and inline source map", function() { it("Should fail with multiple input and inline source map", function() {
assert.throws(function() { var result = Uglify.minify([
Uglify.minify([ read("./test/input/issue-520/input.js"),
read("./test/input/issue-520/input.js"), read("./test/input/issue-520/output.js")
read("./test/input/issue-520/output.js") ], {
], { sourceMap: {
sourceMap: { content: "inline",
content: "inline", url: "inline"
url: "inline" }
}
});
}); });
var err = result.error;
assert.ok(err instanceof Error);
assert.strictEqual(err.stack.split(/\n/)[0], "Error: inline source map only works with singular input");
}); });
}); });
@@ -170,26 +171,14 @@ describe("minify", function() {
}); });
describe("JS_Parse_Error", function() { describe("JS_Parse_Error", function() {
it("should throw syntax error", function() { it("should return syntax error", function() {
assert.throws(function() { var result = Uglify.minify("function f(a{}");
Uglify.minify("function f(a{}"); var err = result.error;
}, function(err) { assert.ok(err instanceof Error);
assert.ok(err instanceof Error); assert.strictEqual(err.stack.split(/\n/)[0], "SyntaxError: Unexpected token punc «{», expected punc «,»");
assert.strictEqual(err.stack.split(/\n/)[0], "SyntaxError: Unexpected token punc «{», expected punc «,»"); assert.strictEqual(err.filename, "0");
assert.strictEqual(err.filename, "0"); assert.strictEqual(err.line, 1);
assert.strictEqual(err.line, 1); assert.strictEqual(err.col, 12);
assert.strictEqual(err.col, 12);
return true;
});
}); });
}); });
describe("Compressor", function() {
it("should be backward compatible with ast.transform(compressor)", function() {
var ast = Uglify.parse("function f(a){for(var i=0;i<a;i++)console.log(i)}");
ast.figure_out_scope();
ast = ast.transform(Uglify.Compressor());
assert.strictEqual(ast.print_to_string(), "function f(a){for(var i=0;i<a;i++)console.log(i)}");
});
})
}); });

View File

@@ -1,5 +1,5 @@
var assert = require("assert"); var assert = require("assert");
var uglify = require("../../"); var uglify = require("../node");
describe("New", function() { describe("New", function() {
it("Should add trailing parentheses for new expressions with zero arguments in beautify mode", function() { it("Should add trailing parentheses for new expressions with zero arguments in beautify mode", function() {

View File

@@ -1,5 +1,5 @@
var assert = require("assert"); var assert = require("assert");
var uglify = require("../../"); var uglify = require("../node");
describe("Number literals", function () { describe("Number literals", function () {
it("Should not allow legacy octal literals in strict mode", function() { it("Should not allow legacy octal literals in strict mode", function() {

View File

@@ -1,4 +1,4 @@
var Uglify = require("../../"); var Uglify = require("../node");
var assert = require("assert"); var assert = require("assert");
describe("Object", function() { describe("Object", function() {

View File

@@ -1,4 +1,4 @@
var UglifyJS = require("../../"); var UglifyJS = require("../node");
var assert = require("assert"); var assert = require("assert");
describe("operator", function() { describe("operator", function() {

View File

@@ -1,10 +1,10 @@
var assert = require("assert"); var assert = require("assert");
var exec = require("child_process").exec; var exec = require("child_process").exec;
var uglify = require("../../"); var uglify = require("../node");
describe("spidermonkey export/import sanity test", function() { describe("spidermonkey export/import sanity test", function() {
it("should produce a functional build when using --self with spidermonkey", function(done) { it("should produce a functional build when using --self with spidermonkey", function(done) {
this.timeout(20000); this.timeout(30000);
var uglifyjs = '"' + process.argv[0] + '" bin/uglifyjs'; var uglifyjs = '"' + process.argv[0] + '" bin/uglifyjs';
var command = uglifyjs + " --self -cm --wrap SpiderUglify -o spidermonkey | " + var command = uglifyjs + " --self -cm --wrap SpiderUglify -o spidermonkey | " +
@@ -15,18 +15,7 @@ describe("spidermonkey export/import sanity test", function() {
eval(stdout); eval(stdout);
assert.strictEqual(typeof SpiderUglify, "object"); assert.strictEqual(typeof SpiderUglify, "object");
assert.strictEqual(SpiderUglify.minify("foo([true,,2+3]);").code, "foo([!0,,5]);");
var ast = SpiderUglify.parse("foo([true,,2+3]);");
assert.strictEqual(true, ast instanceof SpiderUglify.AST_Node);
ast.figure_out_scope();
ast = SpiderUglify.Compressor({}).compress(ast);
assert.strictEqual(true, ast instanceof SpiderUglify.AST_Node);
var stream = SpiderUglify.OutputStream({});
ast.print(stream);
var code = stream.toString();
assert.strictEqual(code, "foo([!0,,5]);");
done(); done();
}); });

View File

@@ -1,4 +1,4 @@
var UglifyJS = require('../../'); var UglifyJS = require("../node");
var assert = require("assert"); var assert = require("assert");
describe("String literals", function() { describe("String literals", function() {

View File

@@ -1,5 +1,5 @@
var assert = require("assert"); var assert = require("assert");
var uglify = require("../../"); var uglify = require("../node");
describe("Template string", function() { describe("Template string", function() {
it("Should not accept invalid sequences", function() { it("Should not accept invalid sequences", function() {

View File

@@ -1,5 +1,5 @@
var assert = require("assert"); var assert = require("assert");
var uglify = require("../../"); var uglify = require("../node");
describe("Try", function() { describe("Try", function() {
it("Should not allow catch with an empty parameter", function() { it("Should not allow catch with an empty parameter", function() {

View File

@@ -1,5 +1,5 @@
var assert = require("assert"); var assert = require("assert");
var uglify = require("../../"); var uglify = require("../node");
describe("Unicode", function() { describe("Unicode", function() {
it("Should not accept invalid code ranges in unicode escape", function() { it("Should not accept invalid code ranges in unicode escape", function() {
@@ -135,4 +135,11 @@ describe("Unicode", function() {
}).code, tests[i][1]); }).code, tests[i][1]);
} }
}); });
it("Should parse raw characters correctly", function() {
var ast = uglify.parse('console.log("\\udbff");');
assert.strictEqual(ast.print_to_string(), 'console.log("\udbff");');
ast = uglify.parse(ast.print_to_string());
assert.strictEqual(ast.print_to_string(), 'console.log("\udbff");');
});
}); });

View File

@@ -1,5 +1,5 @@
var assert = require("assert"); var assert = require("assert");
var uglify = require("../../"); var uglify = require("../node");
describe("With", function() { describe("With", function() {
it("Should throw syntaxError when using with statement in strict mode", function() { it("Should throw syntaxError when using with statement in strict mode", function() {

View File

@@ -1,4 +1,4 @@
var UglifyJS = require("../../"); var UglifyJS = require("../node");
var assert = require("assert"); var assert = require("assert");
describe("Yield", function() { describe("Yield", function() {

View File

@@ -1,103 +1,73 @@
// Testing UglifyJS <-> SpiderMonkey AST conversion // Testing UglifyJS <-> SpiderMonkey AST conversion
// through generative testing. "use strict";
var UglifyJS = require(".."), var acorn = require("acorn");
escodegen = require("escodegen"), var ufuzz = require("./ufuzz");
esfuzz = require("esfuzz"), var UglifyJS = require("..");
estraverse = require("estraverse"),
prefix = "\r ";
// Normalizes input AST for UglifyJS in order to get correct comparison. function try_beautify(code) {
var beautified = UglifyJS.minify(code, {
function normalizeInput(ast) { compress: false,
return estraverse.replace(ast, { mangle: false,
enter: function(node, parent) { output: {
switch (node.type) { beautify: true,
// Internally mark all the properties with semi-standard type "Property". bracketize: true
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;
}
} }
}); });
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) { function test(original, estree, description) {
console.log("--- UglifyJS <-> Mozilla AST conversion"); var transformed = UglifyJS.minify(UglifyJS.AST_Node.from_mozilla_ast(estree), {
compress: false,
for (var counter = 0; counter < options.iterations; counter++) { mangle: false
process.stdout.write(prefix + counter + "/" + options.iterations); });
if (transformed.error || original !== transformed.code) {
var ast1 = normalizeInput(esfuzz.generate({ console.log("//=============================================================");
maxDepth: options.maxDepth console.log("// !!!!!! Failed... round", round);
})); console.log("// original code");
try_beautify(original);
var ast2 = console.log();
UglifyJS console.log();
.AST_Node console.log("//-------------------------------------------------------------");
.from_mozilla_ast(ast1) console.log("//", description);
.to_mozilla_ast(); if (transformed.error) {
console.log(transformed.error.stack);
var astPair = [ } else {
{name: 'expected', value: ast1}, try_beautify(transformed.code);
{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.");
} }
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();

6
test/node.js Normal file
View File

@@ -0,0 +1,6 @@
var fs = require("fs");
new Function("MOZ_SourceMap", "exports", require("../tools/node").FILES.map(function(file) {
if (/exports\.js$/.test(file)) file = require.resolve("./exports");
return fs.readFileSync(file, "utf8");
}).join("\n\n"))(require("source-map"), exports);

View File

@@ -1,6 +1,6 @@
#! /usr/bin/env node #! /usr/bin/env node
var U = require("../tools/node"); var U = require("./node");
var path = require("path"); var path = require("path");
var fs = require("fs"); var fs = require("fs");
var assert = require("assert"); var assert = require("assert");
@@ -23,12 +23,6 @@ mocha_tests();
var run_sourcemaps_tests = require('./sourcemaps'); var run_sourcemaps_tests = require('./sourcemaps');
run_sourcemaps_tests(); run_sourcemaps_tests();
var run_ast_conversion_tests = require("./mozilla-ast");
run_ast_conversion_tests({
iterations: 1000
});
/* -----[ utils ]----- */ /* -----[ utils ]----- */
function tmpl() { function tmpl() {

View File

@@ -1,14 +1,16 @@
var vm = require("vm"); var vm = require("vm");
function safe_log(arg) { function safe_log(arg, level) {
if (arg) switch (typeof arg) { if (arg) switch (typeof arg) {
case "function": case "function":
return arg.toString(); return arg.toString();
case "object": case "object":
if (/Error$/.test(arg.name)) return arg.toString(); if (/Error$/.test(arg.name)) return arg.toString();
arg.constructor.toString(); arg.constructor.toString();
for (var key in arg) { if (level--) for (var key in arg) {
arg[key] = safe_log(arg[key]); if (!Object.getOwnPropertyDescriptor(arg, key).get) {
arg[key] = safe_log(arg[key], level);
}
} }
} }
return arg; return arg;
@@ -48,7 +50,9 @@ exports.run_code = function(code) {
].join("\n"), { ].join("\n"), {
console: { console: {
log: function() { 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 }); }, { timeout: 5000 });

View File

@@ -1,4 +1,4 @@
var UglifyJS = require(".."); var UglifyJS = require("./node");
var ok = require("assert"); var ok = require("assert");
module.exports = function () { module.exports = function () {

View File

@@ -12,7 +12,7 @@
stream._handle.setBlocking(true); stream._handle.setBlocking(true);
}); });
var UglifyJS = require(".."); var UglifyJS = require("./node");
var randomBytes = require("crypto").randomBytes; var randomBytes = require("crypto").randomBytes;
var sandbox = require("./sandbox"); var sandbox = require("./sandbox");
@@ -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 num_iterations = +process.argv[2] || 1/0;
var verbose = false; // log every generated test var verbose = false; // log every generated test
var verbose_interval = false; // log every 100 generated tests var verbose_interval = false; // log every 100 generated tests
var verbose_error = false;
var use_strict = 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) { for (var i = 2; i < process.argv.length; ++i) {
switch (process.argv[i]) { switch (process.argv[i]) {
case '-v': case '-v':
@@ -58,9 +59,6 @@ for (var i = 2; i < process.argv.length; ++i) {
case '-V': case '-V':
verbose_interval = true; verbose_interval = true;
break; break;
case '-E':
verbose_error = true;
break;
case '-t': case '-t':
MAX_GENERATED_TOPLEVELS_PER_RUN = +process.argv[++i]; 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'); 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]; 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'); if (!(STMT_SECOND_LEVEL_OVERRIDE >= 0)) throw new Error('Unknown statement name; use -? to get a list');
break; break;
case '--no-catch-redef':
catch_redef = false;
break;
case '--no-directive':
generate_directive = false;
break;
case '--use-strict': case '--use-strict':
use_strict = true; use_strict = true;
break; 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('<number>: generate this many cases (if used must be first arg)');
console.log('-v: print every generated test case'); console.log('-v: print every generated test case');
console.log('-V: print every 100th 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('-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('-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('-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('-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('--use-strict: generate "use strict"');
console.log('--stmt-depth-from-func: reset statement depth counter at each function, counts from global otherwise'); 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'); 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 = [ var UNARY_SAFE = [
'+', '+',
@@ -276,6 +303,7 @@ var TYPEOF_OUTCOMES = [
'symbol', 'symbol',
'crap' ]; 'crap' ];
var unique_vars = [];
var loops = 0; var loops = 0;
var funcs = 0; var funcs = 0;
var labels = 10000; var labels = 10000;
@@ -290,6 +318,10 @@ function strictMode() {
} }
function createTopLevelCode() { 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 [ return [
strictMode(), strictMode(),
'var a = 100, b = 10, c = 0;', 'var a = 100, b = 10, c = 0;',
@@ -325,33 +357,36 @@ function createArgs() {
return args.join(', '); 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) { function createFunction(recurmax, inGlobal, noDecl, canThrow, stmtDepth) {
if (--recurmax < 0) { return ';'; } if (--recurmax < 0) { return ';'; }
if (!STMT_COUNT_FROM_GLOBAL) stmtDepth = 0; if (!STMT_COUNT_FROM_GLOBAL) stmtDepth = 0;
var func = funcs++; var func = funcs++;
var namesLenBefore = VAR_NAMES.length; var namesLenBefore = VAR_NAMES.length;
var name = (inGlobal || rng(5) > 0) ? 'f' + func : createVarName(MANDATORY, noDecl); var name;
if (name === 'a' || name === 'b' || name === 'c') name = 'f' + func; // quick hack to prevent assignment to func names of being called if (inGlobal || rng(5) > 0) name = 'f' + func;
var s = ''; 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) { if (rng(5) === 0) {
// functions with functions. lower the recursion to prevent a mess. // functions with functions. lower the recursion to prevent a mess.
s = [ s.push(createFunctions(rng(5) + 1, Math.ceil(recurmax * 0.7), NOT_GLOBAL, ANY_TYPE, canThrow, stmtDepth));
'function ' + name + '(' + createParams() + '){',
strictMode(),
createFunctions(rng(5) + 1, Math.ceil(recurmax * 0.7), NOT_GLOBAL, ANY_TYPE, canThrow, stmtDepth),
'}',
''
].join('\n');
} else { } else {
// functions with statements // functions with statements
s = [ s.push(createStatements(3, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth));
'function ' + name + '(' + createParams() + '){',
strictMode(),
createStatements(3, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
'}',
''
].join('\n');
} }
s.push('}', '');
s = filterDirective(s).join('\n');
VAR_NAMES.length = namesLenBefore; VAR_NAMES.length = namesLenBefore;
@@ -359,7 +394,6 @@ function createFunction(recurmax, inGlobal, noDecl, canThrow, stmtDepth) {
// avoid "function statements" (decl inside statements) // avoid "function statements" (decl inside statements)
else if (inGlobal || rng(10) > 0) s += 'var ' + createVarName(MANDATORY) + ' = ' + name + '(' + createArgs() + ');'; else if (inGlobal || rng(10) > 0) s += 'var ' + createVarName(MANDATORY) + ' = ' + name + '(' + createArgs() + ');';
return s; return s;
} }
@@ -406,7 +440,7 @@ function getLabel(label) {
return label && " L" + label; return label && " L" + label;
} }
function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) { function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth, target) {
++stmtDepth; ++stmtDepth;
var loop = ++loops; var loop = ++loops;
if (--recurmax < 0) { 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 // allow to forcefully generate certain structures at first or second recursion level
var target = 0; if (target === undefined) {
if (stmtDepth === 1 && STMT_FIRST_LEVEL_OVERRIDE >= 0) target = STMT_FIRST_LEVEL_OVERRIDE; 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 if (stmtDepth === 2 && STMT_SECOND_LEVEL_OVERRIDE >= 0) target = STMT_SECOND_LEVEL_OVERRIDE;
else target = STMTS_TO_USE[rng(STMTS_TO_USE.length)]; else target = STMTS_TO_USE[rng(STMTS_TO_USE.length)];
}
switch (target) { switch (target) {
case STMT_BLOCK: case STMT_BLOCK:
@@ -460,20 +495,22 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
case STMT_VAR: case STMT_VAR:
switch (rng(3)) { switch (rng(3)) {
case 0: case 0:
unique_vars.push('c');
var name = createVarName(MANDATORY); var name = createVarName(MANDATORY);
if (name === 'c') name = 'a'; unique_vars.pop();
return 'var ' + name + ';'; return 'var ' + name + ';';
case 1: case 1:
// initializer can only have one expression // initializer can only have one expression
unique_vars.push('c');
var name = createVarName(MANDATORY); var name = createVarName(MANDATORY);
if (name === 'c') name = 'b'; unique_vars.pop();
return 'var ' + name + ' = ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';'; return 'var ' + name + ' = ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
default: default:
// initializer can only have one expression // initializer can only have one expression
unique_vars.push('c');
var n1 = createVarName(MANDATORY); var n1 = createVarName(MANDATORY);
if (n1 === 'c') n1 = 'b';
var n2 = createVarName(MANDATORY); 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) + ';'; return 'var ' + n1 + ' = ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ', ' + n2 + ' = ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
} }
case STMT_RETURN_ETC: case STMT_RETURN_ETC:
@@ -514,8 +551,11 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
var nameLenBefore = VAR_NAMES.length; var nameLenBefore = VAR_NAMES.length;
var catchName = createVarName(MANDATORY); var catchName = createVarName(MANDATORY);
var freshCatchName = VAR_NAMES.length !== nameLenBefore; var freshCatchName = VAR_NAMES.length !== nameLenBefore;
if (!catch_redef) unique_vars.push(catchName);
s += ' catch (' + catchName + ') { ' + createStatements(3, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + ' }'; 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) + ' }'; if (n !== 0) s += ' finally { ' + createStatements(3, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + ' }';
return s; return s;
@@ -593,8 +633,9 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
case p++: case p++:
case p++: case p++:
var nameLenBefore = VAR_NAMES.length; 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. 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 = []; var s = [];
switch (rng(5)) { switch (rng(5)) {
case 0: case 0:
@@ -636,7 +677,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
strictMode() strictMode()
); );
if (instantiate) for (var i = rng(4); --i >= 0;) { 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) + ';'); else s.push('this[' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']' + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ';');
} }
s.push( s.push(
@@ -646,7 +687,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
break; break;
} }
VAR_NAMES.length = nameLenBefore; VAR_NAMES.length = nameLenBefore;
return s.join('\n'); return filterDirective(s).join('\n');
case p++: case p++:
case p++: case p++:
return createTypeofExpr(recurmax, stmtDepth, canThrow); return createTypeofExpr(recurmax, stmtDepth, canThrow);
@@ -689,19 +730,19 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
") || " + rng(10) + ").toString()[" + ") || " + rng(10) + ").toString()[" +
createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "] "; createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "] ";
case p++: case p++:
return createArrayLiteral(recurmax, COMMA_OK, stmtDepth, canThrow); return createArrayLiteral(recurmax, stmtDepth, canThrow);
case p++: case p++:
return createObjectLiteral(recurmax, COMMA_OK, stmtDepth, canThrow); return createObjectLiteral(recurmax, stmtDepth, canThrow);
case p++: case p++:
return createArrayLiteral(recurmax, COMMA_OK, stmtDepth, canThrow) + '[' + return createArrayLiteral(recurmax, stmtDepth, canThrow) + '[' +
createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']'; createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']';
case p++: case p++:
return createObjectLiteral(recurmax, COMMA_OK, stmtDepth, canThrow) + '[' + return createObjectLiteral(recurmax, stmtDepth, canThrow) + '[' +
createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']'; createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']';
case p++: case p++:
return createArrayLiteral(recurmax, COMMA_OK, stmtDepth, canThrow) + '.' + getDotKey(); return createArrayLiteral(recurmax, stmtDepth, canThrow) + '.' + getDotKey();
case p++: case p++:
return createObjectLiteral(recurmax, COMMA_OK, stmtDepth, canThrow) + '.' + getDotKey(); return createObjectLiteral(recurmax, stmtDepth, canThrow) + '.' + getDotKey();
case p++: case p++:
var name = getVarName(); var name = getVarName();
return name + ' && ' + name + '[' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']'; 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); return _createExpression(recurmax, noComma, stmtDepth, canThrow);
} }
function createArrayLiteral(recurmax, noComma, stmtDepth, canThrow) { function createArrayLiteral(recurmax, stmtDepth, canThrow) {
recurmax--; recurmax--;
var arr = "["; var arr = "[";
for (var i = rng(6); --i >= 0;) { for (var i = rng(6); --i >= 0;) {
@@ -746,18 +787,56 @@ var KEYS = [
"3", "3",
].concat(SAFE_KEYS); ].concat(SAFE_KEYS);
function getDotKey() { function getDotKey(assign) {
return SAFE_KEYS[rng(SAFE_KEYS.length)]; var key;
do {
key = SAFE_KEYS[rng(SAFE_KEYS.length)];
} while (assign && key == "length");
return key;
} }
function createObjectLiteral(recurmax, noComma, stmtDepth, canThrow) { function createAccessor(recurmax, stmtDepth, canThrow) {
recurmax--; var namesLenBefore = VAR_NAMES.length;
var obj = "({"; var s;
for (var i = rng(6); --i >= 0;) { var prop1 = getDotKey();
var key = KEYS[rng(KEYS.length)]; if (rng(2) == 0) {
obj += key + ":(" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "), "; 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) { function createNestedBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
@@ -787,7 +866,7 @@ function _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
return canThrow && rng(10) == 0 ? expr : '(' + assignee + ' && ' + expr + ')'; return canThrow && rng(10) == 0 ? expr : '(' + assignee + ' && ' + expr + ')';
case 4: case 4:
assignee = getVarName(); assignee = getVarName();
expr = '(' + assignee + '.' + getDotKey() + createAssignment() expr = '(' + assignee + '.' + getDotKey(true) + createAssignment()
+ _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')'; + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')';
return canThrow && rng(10) == 0 ? expr : '(' + assignee + ' && ' + expr + ')'; return canThrow && rng(10) == 0 ? expr : '(' + assignee + ' && ' + expr + ')';
default: default:
@@ -844,35 +923,40 @@ function getVarName() {
function createVarName(maybe, dontStore) { function createVarName(maybe, dontStore) {
if (!maybe || rng(2)) { if (!maybe || rng(2)) {
var name = VAR_NAMES[rng(VAR_NAMES.length)];
var suffix = rng(3); var suffix = rng(3);
if (suffix) { var name;
name += '_' + suffix; do {
if (!dontStore) VAR_NAMES.push(name); 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 name;
} }
return ''; return '';
} }
if (require.main !== module) {
exports.createTopLevelCode = createTopLevelCode;
exports.num_iterations = num_iterations;
return;
}
function try_beautify(code, result) { function try_beautify(code, result) {
try { var beautified = UglifyJS.minify(code, {
var beautified = UglifyJS.minify(code, { compress: false,
compress: false, mangle: false,
mangle: false, output: {
output: { beautify: true,
beautify: true, bracketize: true,
bracketize: true, },
}, });
}).code; if (beautified.error) {
if (sandbox.same_stdout(sandbox.run_code(beautified), result)) {
console.log("// (beautified)");
console.log(beautified);
return;
}
} catch (e) {
console.log("// !!! beautify failed !!!"); console.log("// !!! beautify failed !!!");
console.log(e.stack); console.log(beautified.error.stack);
} else if (sandbox.same_stdout(sandbox.run_code(beautified.code), result)) {
console.log("// (beautified)");
console.log(beautified.code);
return;
} }
console.log("//"); console.log("//");
console.log(code); console.log(code);
@@ -908,12 +992,13 @@ function log_suspects(minify_options, component) {
var o = JSON.parse(JSON.stringify(options)); var o = JSON.parse(JSON.stringify(options));
o[name] = false; o[name] = false;
m[component] = o; m[component] = o;
try { var result = UglifyJS.minify(original_code, m);
var r = sandbox.run_code(UglifyJS.minify(original_code, m).code); if (result.error) {
return sandbox.same_stdout(original_result, r);
} catch (e) {
console.log("Error testing options." + component + "." + name); console.log("Error testing options." + component + "." + name);
console.log(e); console.log(result.error);
} else {
var r = sandbox.run_code(result.code);
return sandbox.same_stdout(original_result, r);
} }
} }
}); });
@@ -974,28 +1059,22 @@ var uglify_code, uglify_result, ok;
for (var round = 1; round <= num_iterations; round++) { for (var round = 1; round <= num_iterations; round++) {
process.stdout.write(round + " of " + num_iterations + "\r"); 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_code = createTopLevelCode();
original_result = sandbox.run_code(original_code); original_result = sandbox.run_code(original_code);
(typeof original_result != "string" ? fallback_options : minify_options).forEach(function(options) { (typeof original_result != "string" ? fallback_options : minify_options).forEach(function(options) {
try { uglify_code = UglifyJS.minify(original_code, JSON.parse(options));
uglify_code = UglifyJS.minify(original_code, JSON.parse(options)).code; if (!uglify_code.error) {
} catch (e) { uglify_code = uglify_code.code;
uglify_code = e;
}
ok = typeof uglify_code == "string";
if (ok) {
uglify_result = sandbox.run_code(uglify_code); uglify_result = sandbox.run_code(uglify_code);
ok = sandbox.same_stdout(original_result, uglify_result); ok = sandbox.same_stdout(original_result, uglify_result);
} else if (typeof original_result != "string") { } else {
ok = uglify_code.name == original_result.name; uglify_code = uglify_code.error;
if (typeof original_result != "string") {
ok = uglify_code.name == original_result.name;
}
} }
if (verbose || (verbose_interval && !(round % INTERVAL_COUNT)) || !ok) log(options); 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("//=============================================================");
console.log("// original code"); console.log("// original code");
try_beautify(original_code, original_result); try_beautify(original_code, original_result);

View File

@@ -1,15 +1,4 @@
exports["Compressor"] = Compressor;
exports["Dictionary"] = Dictionary; exports["Dictionary"] = Dictionary;
exports["JS_Parse_Error"] = JS_Parse_Error;
exports["OutputStream"] = OutputStream;
exports["SourceMap"] = SourceMap;
exports["TreeWalker"] = TreeWalker; exports["TreeWalker"] = TreeWalker;
exports["base54"] = base54;
exports["defaults"] = defaults;
exports["mangle_properties"] = mangle_properties;
exports["minify"] = minify; exports["minify"] = minify;
exports["parse"] = parse; exports["_push_uniq"] = push_uniq;
exports["push_uniq"] = push_uniq;
exports["string_template"] = string_template;
exports["tokenizer"] = tokenizer;
exports["is_identifier"] = is_identifier;

View File

@@ -18,15 +18,19 @@ var FILES = UglifyJS.FILES = [
return require.resolve(file); return require.resolve(file);
}); });
new Function("MOZ_SourceMap", "exports", FILES.map(function(file){ new Function("MOZ_SourceMap", "exports", function() {
return fs.readFileSync(file, "utf8"); var code = FILES.map(function(file) {
}).join("\n\n"))( return fs.readFileSync(file, "utf8");
});
code.push("exports.describe_ast = " + describe_ast.toString());
return code.join("\n\n");
}())(
require("source-map"), require("source-map"),
UglifyJS UglifyJS
); );
UglifyJS.describe_ast = function() { function describe_ast() {
var out = UglifyJS.OutputStream({ beautify: true }); var out = OutputStream({ beautify: true });
function doitem(ctor) { function doitem(ctor) {
out.print("AST_" + ctor.TYPE); out.print("AST_" + ctor.TYPE);
var props = ctor.SELF_PROPS.filter(function(prop){ var props = ctor.SELF_PROPS.filter(function(prop){
@@ -56,6 +60,6 @@ UglifyJS.describe_ast = function() {
}); });
} }
}; };
doitem(UglifyJS.AST_Node); doitem(AST_Node);
return out + ""; return out + "";
}; }