Compare commits

...

62 Commits

Author SHA1 Message Date
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
Alex Lam S.L
2d99d06601 update documentation
Remove deprecated CLI option
2017-05-07 03:02:46 +08:00
Alex Lam S.L
98cf95e5b5 fix test for #1865 (#1873) 2017-05-07 02:56:02 +08:00
Alex Lam S.L
7313465cba v3.0.0 2017-05-06 23:51:10 +08:00
Alex Lam S.L
2c7ee956fd fix unsafe on evaluate of reduce_vars (#1870)
Determine if variables with non-constant values can escape and be modified.

fixes #1865
2017-05-06 23:18:55 +08:00
Alex Lam S.L
ecf3563c45 kill opera (#1869) 2017-05-06 17:42:07 +08:00
Alex Lam S.L
dee5a27516 enhance collapse_vars (#1862)
- extend expression types
  - `a++`
  - `a=x;`
- extend scan range
  - `for(init;;);`
  - `switch(expr){case expr:}`
  - `a = x; a = a || y;`
- terminate upon `debugger;`

closes #1821
fixes #27
fixes #315
fixes #1858
2017-05-06 16:15:43 +08:00
Alex Lam S.L
5a25d24b56 rename variables for better readability (#1863) 2017-05-02 20:47:10 +08:00
Alex Lam S.L
bffdc8dca8 update test/benchmark.js resources (#1864) 2017-05-02 19:48:12 +08:00
Alex Lam S.L
69b5663653 restore report of supported options (#1861)
fixes #1859
2017-05-02 01:42:29 +08:00
kzc
ea9289771b improve literal return optimization (#1860) 2017-05-02 00:10:11 +08:00
Alex Lam S.L
2cb55b2ad0 enforce toplevel on other compress options (#1855)
Respect "funcs" and "vars" properly.

fixes #1850
2017-04-30 22:52:36 +08:00
kzc
bbb5f2a89c Update ISSUE_TEMPLATE.md (#1846) 2017-04-26 01:30:43 +08:00
Alex Lam S.L
76d19b60ad fix fuzzer on this (#1842)
- forbid redeclaration of `this`
- suppress probability for `this` within nested functions
2017-04-24 03:15:03 +08:00
Alex Lam S.L
9e62628171 fix unused on for-in statements (#1843)
Only need to avoid `var` within the initialisation block.

fixes #1841
2017-04-24 03:14:01 +08:00
Alex Lam S.L
9bf72cf758 improve parser under "use strict" (#1836)
- `const` without value
- `delete` of expression
- redefining `arguments` or `eval`

extend `test/ufuzz.js`
- optionally generate "use strict"
- improve handling of test cases with syntax errors
- group IIFE generation
- generate bare anonymous functions
- workaround `console.log()` for `new function()`
- generate expressions with `this`


fixes #1810
2017-04-23 20:05:22 +08:00
kzc
64d74432f6 update README for 3.x (#1840) 2017-04-23 04:28:32 +08:00
Alex Lam S.L
45ce369480 fix AST_For.init patch-up in drop_unused() (#1839)
fixes #1838
2017-04-23 01:51:56 +08:00
Alex Lam S.L
ca32a09032 fix label-related bugs (#1835)
- deep cloning of `AST_LabeledStatement`
- `L:do{...}while(false)`
- empty statement with label within block

extend `test/ufuzz.js`
- generate labels for blocks & loops
- generate for-in statements
- skip suspicious option search if `minify()` errs


fixes #1833
2017-04-22 22:15:04 +08:00
Roman Dvornov
6f954aa3d0 Fix API reference examples (#1834) 2017-04-21 02:23:41 +08:00
Alex Lam S.L
f05d4f7af3 improve unused (#1832)
- extract leading value with side-effects out of `var` statement
- reduce scanning of `AST_Definitions` from 3 passes to just once
2017-04-20 13:06:14 +08:00
Alex Lam S.L
88e7a542cd fix unused on labeled for-loop (#1831)
fixes #1830
2017-04-20 04:18:38 +08:00
Alex Lam S.L
4dcff038cb improve collapse_vars on AST_Var (#1828)
Perform the same cascaded scanning within `var` statement as we do on array of statements.
2017-04-19 04:49:09 +08:00
Alex Lam S.L
b4b9305db0 fix parser bugs & CLI reporting (#1827)
fixes #1825
2017-04-19 04:27:13 +08:00
Alex Lam S.L
28cfb65c47 extend cascade into a.b (#1829)
fixes #27
2017-04-19 04:17:15 +08:00
Alex Lam S.L
0f4f01b66c clean up collapse_vars (#1826)
- remove overlap in functionality of singular, consecutive reference of constant value
- remove workarounds for previous bugs in `lib/scope.js`
- distribute recursive `collapse_single_use_vars()` calls to their respective `OPT(AST_Node)`
- enable collapsing of variables within a single `AST_Definitions`
2017-04-18 21:45:34 +08:00
Alex Lam S.L
5d9f1da3ab support safe reassignments in reduce_vars (#1823)
`var a=1;a=2;x(a)` => `x(2)`

fix pre-existing issues
- reference counting on assignment
- walking of anonymous functions
- chained assignment
2017-04-18 13:38:42 +08:00
Alex Lam S.L
d1aa09c5c7 fix reduce_vars on conditionals (#1822) 2017-04-18 01:44:23 +08:00
Alex Lam S.L
6d5f341999 fix reduce_vars on boolean binary expressions (#1819)
Side effects of `&&` and `||` have not mattered until #1814, which takes assignment expressions into account.
2017-04-17 17:24:29 +08:00
Alex Lam S.L
4ffb6fce76 compress duplicated variable definitions (#1817)
These are surprisingly common, as people reuse the same variable name within loops or switch branches.
2017-04-17 17:11:29 +08:00
Alex Lam S.L
71a8d0d236 fix reduce_vars within try-block (#1818)
Possible partial execution due to exceptions.
2017-04-17 14:03:29 +08:00
Alex Lam S.L
1a498db2d3 enhance reduce_vars (#1814)
- allow immediate assignment after declaration of variable
- relax modification rule for immutable value
- fix order of visit for TreeWalker
- remove extraneous code
2017-04-17 01:36:50 +08:00
Alex Lam S.L
44dfa5a318 fix variable substitution (#1816)
- let `collapse_vars` take care of value containing any symbols
- improve overhead accounting
2017-04-16 17:25:39 +08:00
Alex Lam S.L
251ff1d1af update README (#1813)
- mention major version bump
- remove reference to internal API
2017-04-16 04:04:28 +08:00
Alex Lam S.L
ec443e422c unify CLI & API under minify() (#1811)
- rename `screw_ie8` to `ie8`
- rename `mangle.except` to `mangle.reserved`
- rename `mangle.properties.ignore_quoted` to `mangle.properties.keep_quoted` 
- compact `sourceMap` options
- more stringent verification on input `options`
- toplevel shorthands
  - `ie8`
  - `keep_fnames`
  - `toplevel`
  - `warnings`
- support arrays and unquoted string values on CLI
- drop `fromString` from `minify()`
  - `minify()` no longer handles any `fs` operations
- unify order of operations for `mangle_properties()` on CLI & API
  - `bin/uglifyjs` used to `mangle_properties()` before even `Compressor`
  - `minify()` used to `mangle_properties()` after `Compressor` but before `mangle_names()`
  - both will now do `Compressor`, `mangle_names()` then `mangle_properties()`
- `options.parse` / `--parse` for parser options beyond `bare_returns`
- add `mangle.properties.builtins` to disable built-in reserved list
  - disable with `--mangle-props builtins` on CLI
- `warnings` now off by default
- add `--warn` and `--verbose` on CLI
- drop `--enclose`
- drop `--export-all`
- drop `--reserved-file`
  - use `--mangle reserved` instead
- drop `--reserve-domprops`
  - enabled by default, disable with `--mangle-props domprops`
- drop `--prefix`
  - use `--source-map base` instead
- drop `--lint`
- remove `bin/extract-props.js`
- limit exposure of internal APIs
- update documentations

closes #96
closes #102
closes #136
closes #166
closes #243
closes #254
closes #261
closes #311
closes #700
closes #748
closes #912
closes #1072
closes #1366
fixes #101
fixes #123
fixes #124
fixes #263
fixes #379
fixes #419
fixes #423
fixes #461
fixes #465
fixes #576
fixes #737
fixes #772
fixes #958
fixes #1036
fixes #1142
fixes #1175
fixes #1220
fixes #1223
fixes #1280
fixes #1359
fixes #1368
2017-04-15 23:50:50 +08:00
Alex Lam S.L
32deb365d5 drop angular (#1812)
Remove support for `@ngInject` as there are proper alternatives anyway.
2017-04-15 05:52:29 +08:00
Alex Lam S.L
2244743545 convert AST_Seq from binary tree to array (#1460)
- rename `AST_Seq` to `AST_Sequence`
- raise default sequences_limit from 200 to 800
2017-04-12 21:56:27 +08:00
Alex Lam S.L
04b8964505 v2.8.22 2017-04-09 11:36:57 +08:00
Alex Lam S.L
d6fbc365e2 fix LHS cases for NaN & friends (#1804)
`Infinity = beyond` should not become `1/0 = beyond`
2017-04-09 03:18:14 +08:00
Alex Lam S.L
9a978843f5 enhance test/ufuzz.js (#1803)
- `-E` to report test cases with runtime errors
- favor returning expressions rather than empty return
- emit a newline upon fuzzer completion to not erase the iteration count

closes #1800
2017-04-09 01:36:38 +08:00
Alex Lam S.L
0479ff0c54 fix a couple of bugs in global_defs (#1802)
- `optimize()` substituted expression
- compute nested property string correctly

fixes #1801

Miscellaneous
- reset optimisation flags on all node types
2017-04-08 16:46:25 +08:00
Alex Lam S.L
cf72fe552f fix delete corner cases (#1799)
- assignment
- boolean
- conditional
- sequence
2017-04-08 14:25:28 +08:00
Alex Lam S.L
a1532eb076 extend ufuzz generator (#1783)
- property access
- property assignment
- allow bare expression within try-block
- normalise `Error` in `console.log()`
- generate more unary expressions
- add parenthesis to enforce precedence
- adjust variable reuse/creation
- add parameters to function declaration & expression
- add return expression
- add trivial arguments to function call
2017-04-07 18:47:30 +08:00
Alex Lam S.L
c2a1bceb77 fix pure_getters for chained property access (#1798) 2017-04-07 17:06:01 +08:00
Alex Lam S.L
e3c9c22c75 fix corner cases with delete (#1796)
`delete Infinity` returns `false` where as `delete (1/0)` returns `true`
2017-04-07 15:39:59 +08:00
Alex Lam S.L
0f4cd73dcc introduce "strict" to pure_getters (#1795) 2017-04-07 13:31:58 +08:00
Alex Lam S.L
281e882d27 fix reduce_vars on catch variable (#1794)
Improved catch handling in `figure_out_scope()` means special case treatment of IE8 is no longer valid in `reset_opt_flags()`.

Also fixed recursive assignment in variable definition.
2017-04-07 12:32:56 +08:00
Alex Lam S.L
cc6aa3e5ac fix incorrect context in variable substitution (#1791)
`AST_Node.optimize()` is context-aware, so don't cache its results to be used elsewhere.

Also fixed a few cases of AST corruption and beef up safety of `pure_getters`.
2017-04-07 03:42:17 +08:00
Alex Lam S.L
e869779a98 enable inline_script by default (#1793) 2017-04-07 00:45:51 +08:00
Alex Lam S.L
06cdb74279 improve pure_getters (#1786)
- property access to `null` & `undefined` always has side effects
- utilise `reduce_vars` to determine safe property access
- may-be cases treated as side effects unless `unsafe`
2017-04-06 11:18:59 +08:00
Alex Lam S.L
ff289b90a9 implement delayed resolution for reduce_vars (#1788)
Although it would be nice to enforce `AST_Node` cloning during transformation, that ship has sailed a long time ago.

We now get the assigned value when resolving `AST_SymbolRef` instead of `reset_opt_flags()`, which has the added advantage of improved compressor efficiency.

fixes #1787
2017-04-05 21:06:42 +08:00
Alex Lam S.L
9b6bc67c33 optimise do{...}while(false) (#1785)
- better heuristics to avoid issues like #1532
- fix `TreeWalker.loopcontrol_target()`
  - `continue` cannot refer to `switch` blocks
2017-04-04 23:48:22 +08:00
Alex Lam S.L
4b90dc1fdb remove --mangle-props from fuzzing (#1777)
The inherently unsafe nature makes this feature unsuitable to be tested this way.

fixes #1774
2017-04-04 16:24:16 +08:00
Alex Lam S.L
951770fc68 exclude mangling of special property names (#1779)
- `null`
- `true`
- `false`
- numeric literals
2017-04-04 03:50:19 +08:00
Alex Lam S.L
48b3fe9952 fix mangleProperties on identifiers (#1776)
- fix handling of "-Infinity"
- add test case for "-0"

reverts #1481
2017-04-03 23:17:47 +08:00
Alex Lam S.L
a400741868 workaround Node.js bugs (#1775)
Wrap test code in IIFE before passing to `vm`

fixes #1768
fixes #1771
2017-04-03 18:56:11 +08:00
Alex Lam S.L
59a4e56bc8 fix mangleProperties of undefined & Infinity (#1772)
`NaN` already works by the happy accident of `Number.NaN`

fixes #1770
2017-04-03 12:31:05 +08:00
Alex Lam S.L
1f1fccc45d extend test/ufuzz.js (#1769)
New expressions:
- property access
- array literal
- object literal

Miscellaneous:
- reduce execution timeout
- test `toplevel` and `mangleProperties`
2017-04-03 04:00:33 +08:00
89 changed files with 11151 additions and 8832 deletions

View File

@@ -1,8 +1,19 @@
- Bug report or feature request? <!-- Note: sub-optimal but correct code is not a bug --> **Bug report or feature request?**
- `uglify-js` version (`uglifyjs -V`)
- JavaScript input - ideally as small as possible. <!-- Note: sub-optimal but correct code is not a bug -->
- The `uglifyjs` CLI command executed or `minify()` options used.
- An example of JavaScript output produced and/or the error or warning. **ES5 or ES6+ input?**
<!-- Note: for ES6 see: https://github.com/mishoo/UglifyJS2/tree/harmony#harmony -->
**`uglify-js` version (`uglifyjs -V`)**
**JavaScript input - ideally as small as possible.**
**The `uglifyjs` CLI command executed or `minify()` options used.**
**JavaScript output produced and/or the error or warning.**
<!-- <!--
Note: the release version of uglify-js only supports ES5. Those wishing Note: the release version of uglify-js only supports ES5. Those wishing
to minify ES6 should use the experimental harmony branch. to minify ES6 should use the experimental harmony branch.

570
README.md
View File

@@ -1,17 +1,13 @@
UglifyJS 2 UglifyJS 3
========== ==========
[![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. UglifyJS is a JavaScript parser, minifier, compressor or beautifier toolkit.
This page documents the command line utility. For
[API and internals documentation see my website](http://lisperator.net/uglifyjs/).
There's also an
[in-browser online demo](http://lisperator.net/uglifyjs/#demo) (for Firefox,
Chrome and probably Safari).
#### Note: #### Note:
- release versions of `uglify-js` only support ECMAScript 5 (ES5). If you wish to minify - **`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)**.
- **Documentation for UglifyJS `2.x` releases can be found [here](https://github.com/mishoo/UglifyJS2/tree/v2.x)**.
- 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. 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. - Node 7 has a known performance regression and runs `uglify-js` twice as slow.
@@ -40,14 +36,13 @@ Usage
uglifyjs [input files] [options] uglifyjs [input files] [options]
UglifyJS2 can take multiple input files. It's recommended that you pass the UglifyJS can take multiple input files. It's recommended that you pass the
input files first, then pass the options. UglifyJS will parse input files input files first, then pass the options. UglifyJS will parse input files
in sequence and apply any compression options. The files are parsed in the in sequence and apply any compression options. The files are parsed in the
same global scope, that is, a reference from a file to some same global scope, that is, a reference from a file to some
variable/function declared in another file will be matched properly. variable/function declared in another file will be matched properly.
If you want to read from STDIN instead, pass a single dash instead of input If no input file is specified, UglifyJS will read from STDIN.
files.
If you wish to pass your options before the input files, separate the two with If you wish to pass your options before the input files, separate the two with
a double dash to prevent input files being used as option arguments: a double dash to prevent input files being used as option arguments:
@@ -57,48 +52,53 @@ a double dash to prevent input files being used as option arguments:
The available options are: The available options are:
``` ```
--source-map Specify an output file where to generate source -h, --help Print usage information.
map. -V, --version Print version number.
--source-map-root The path to the original source to be included -p, --parse <options> Specify parser options:
in the source map. `acorn` Use Acorn for parsing.
--source-map-url The path to the source map to be added in //# `bare_returns` Allow return outside of functions.
sourceMappingURL. Defaults to the value passed Useful when minifying CommonJS
with --source-map. modules and Userscripts that may
--source-map-include-sources Pass this flag if you want to include the be anonymous function wrapped (IIFE)
content of source files in the source map as by the .user.js engine `caller`.
sourcesContent property. `expression` Parse a single expression, rather than
--source-map-inline Write base64-encoded source map to the end of js output. a program (for parsing JSON).
--in-source-map Input source map, useful if you're compressing `spidermonkey` Assume input files are SpiderMonkey
JS that was generated from some other original AST format (as JSON).
code. Specify "inline" if the source map is included -c, --compress [options] Enable compressor/specify compressor options:
inline with the sources. `pure_funcs` List of functions that can be safely
--screw-ie8 Use this flag if you don't wish to support removed when their return values are
Internet Explorer 6/7/8. not used.
By default UglifyJS will not try to be IE-proof. -m, --mangle [options] Mangle names/specify mangler options:
--support-ie8 Use this flag to support Internet Explorer 6/7/8. `reserved` List of names that should not be mangled.
Equivalent to setting `screw_ie8: false` in `minify()` --mangle-props [options] Mangle properties/specify mangler options:
for `compress`, `mangle` and `output` options. `builtins` Mangle property names that overlaps
--expr Parse a single expression, rather than a with standard JavaScript globals.
program (for parsing JSON) `debug` Add debug prefix and suffix.
-p, --prefix Skip prefix for original filenames that appear `domprops` Mangle property names that overlaps
in source maps. For example -p 3 will drop 3 with DOM properties.
directories from file names and ensure they are `keep_quoted` Only mangle unquoted properies.
relative paths. You can also specify -p `regex` Only mangle matched property names.
relative, which will make UglifyJS figure out `reserved` List of names that should not be mangled.
itself the relative paths between original -b, --beautify [options] Beautify output/specify output options:
sources, the source map and the output file. `beautify` Enabled with `--beautify` by default.
-o, --output Output file (default STDOUT). `preamble` Preamble to prepend to the output. You
-b, --beautify Beautify output/specify output options. can use this to insert a comment, for
-m, --mangle Mangle names/pass mangler options. example for licensing information.
-r, --reserved Reserved names to exclude from mangling. This will not be parsed, but the source
-c, --compress Enable compressor/pass compressor options, e.g. map will adjust for its presence.
`-c 'if_return=false,pure_funcs=["Math.pow","console.log"]'` `quote_style` Quote style:
Use `-c` with no argument to enable default compression 0 - auto
options. 1 - single
-d, --define Global definitions 2 - double
-e, --enclose Embed everything in a big function, with a 3 - original
configurable parameter/argument list. `wrap_iife` Wrap IIFEs in parenthesis. Note: you may
--comments Preserve copyright comments in the output. By want to disable `negate_iife` under
compressor options.
-o, --output <file> Output file path (default STDOUT). Specify `ast` or
`spidermonkey` to write UglifyJS or SpiderMonkey AST
as JSON to STDOUT respectively.
--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
"@preserve". You can optionally pass one of the "@preserve". You can optionally pass one of the
@@ -110,54 +110,39 @@ The available options are:
kept when compression is on, because of dead kept when compression is on, because of dead
code removal or cascading statements into code removal or cascading statements into
sequences. sequences.
--preamble Preamble to prepend to the output. You can use --config-file <file> Read `minify()` options from JSON file.
this to insert a comment, for example for -d, --define <expr>[=value] Global definitions.
licensing information. This will not be --ie8 Support non-standard Internet Explorer 8.
parsed, but the source map will adjust for its Equivalent to setting `ie8: true` in `minify()`
presence. for `compress`, `mangle` and `output` options.
--stats Display operations run time on STDERR. By default UglifyJS will not try to be IE-proof.
--acorn Use Acorn for parsing. --keep-fnames Do not mangle/drop function names. Useful for
--spidermonkey Assume input files are SpiderMonkey AST format code relying on Function.prototype.name.
(as JSON). --name-cache File to hold mangled name mappings.
--self Build itself (UglifyJS2) as a library (implies --self Build UglifyJS as a library (implies --wrap UglifyJS)
--wrap=UglifyJS --export-all) --source-map [options] Enable source map/specify source map options:
--wrap Embed everything in a big function, making the `base` Path to compute relative paths from input files.
`content` Input source map, useful if you're compressing
JS that was generated from some other original
code. Specify "inline" if the source map is
included within the sources.
`filename` Name and/or location of the output source.
`includeSources` Pass this flag if you want to include
the content of source files in the
source map as sourcesContent property.
`root` Path to the original source to be included in
the source map.
`url` If specified, path to the source map to append in
`//# sourceMappingURL`.
--stats Display operations run time on STDERR.
--toplevel Compress and/or mangle variables in toplevel scope.
--verbose Print diagnostic messages.
--warn Print warning messages.
--wrap <name> Embed everything in a big function, making the
“exports” and “global” variables available. You “exports” and “global” variables available. You
need to pass an argument to this option to need to pass an argument to this option to
specify the name that your module will take specify the name that your module will take
when included in, say, a browser. when included in, say, a browser.
--export-all Only used when --wrap, this tells UglifyJS to
add code to automatically export all globals.
--lint Display some scope warnings
-v, --verbose Verbose
-V, --version Print version number and exit.
--noerr Don't throw an error for unknown options in -c,
-b or -m.
--bare-returns Allow return outside of functions. Useful when
minifying CommonJS modules and Userscripts that
may be anonymous function wrapped (IIFE) by the
.user.js engine `caller`.
--keep-fnames Do not mangle/drop function names. Useful for
code relying on Function.prototype.name.
--reserved-file File containing reserved names
--reserve-domprops Make (most?) DOM properties reserved for
--mangle-props
--mangle-props Mangle property names (default `0`). Set to
`true` or `1` to mangle all property names. Set
to `unquoted` or `2` to only mangle unquoted
property names. Mode `2` also enables the
`keep_quoted_props` beautifier option to
preserve the quotes around property names and
disables the `properties` compressor option to
prevent rewriting quoted properties with dot
notation. You can override these by setting
them explicitly on the command line.
--mangle-regex Only mangle property names matching the regex
--name-cache File to hold mangled names mappings
--pure-funcs Functions that can be safely removed if their
return value is not used, e.g.
`--pure-funcs Math.floor console.info`
(requires `--compress`)
``` ```
Specify `--output` (`-o`) to declare the output file. Otherwise the output Specify `--output` (`-o`) to declare the output file. Otherwise the output
@@ -165,25 +150,21 @@ goes to STDOUT.
## Source map options ## Source map options
UglifyJS2 can generate a source map file, which is highly useful for UglifyJS can generate a source map file, which is highly useful for
debugging your compressed JavaScript. To get a source map, pass debugging your compressed JavaScript. To get a source map, pass
`--source-map output.js.map` (full path to the file where you want the `--source-map --output output.js` (source map will be written out to
source map dumped). `output.js.map`).
Additionally you might need `--source-map-root` to pass the URL where the Additionally you might need `--source-map root=<URL>` to pass the URL where
original files can be found. In case you are passing full paths to input the original files can be found. Use `--source-map url=<URL>` to specify
files to UglifyJS, you can use `--prefix` (`-p`) to specify the number of the URL where the source map can be found.
directories to drop from the path prefix when declaring files in the source
map.
For example: For example:
uglifyjs /home/doe/work/foo/src/js/file1.js \ uglifyjs /home/doe/work/foo/src/js/file1.js \
/home/doe/work/foo/src/js/file2.js \ /home/doe/work/foo/src/js/file2.js \
-o foo.min.js \ -o foo.min.js -c -m \
--source-map foo.min.js.map \ --source-map base="/home/doe/work/foo/src",root="http://foo.com/src"
--source-map-root http://foo.com/src \
-p 5 -c -m
The above will compress and mangle `file1.js` and `file2.js`, will drop the 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
@@ -220,10 +201,10 @@ To enable the mangler you need to pass `--mangle` (`-m`). The following
(disabled by default). (disabled by default).
When mangling is enabled but you want to prevent certain names from being When mangling is enabled but you want to prevent certain names from being
mangled, you can declare those names with `--reserved` (`-r`) — pass a mangled, you can declare those names with `--mangle reserved` — pass a
comma-separated list of names. For example: comma-separated list of names. For example:
uglifyjs ... -m -r '$,require,exports' uglifyjs ... -m reserved=[$,require,exports]
to prevent the `require`, `exports` and `$` names from being changed. to prevent the `require`, `exports` and `$` names from being changed.
@@ -248,39 +229,22 @@ console.log(x.something());
In the above code, `foo`, `bar`, `baz`, `moo` and `boo` will be replaced In the above code, `foo`, `bar`, `baz`, `moo` and `boo` will be replaced
with single characters, while `something()` will be left as is. with single characters, while `something()` will be left as is.
In order for this to be of any use, we should avoid mangling standard JS In order for this to be of any use, we avoid mangling standard JS names by
names. For instance, if your code would contain `x.length = 10`, then default (`--mangle-props builtins` to override).
`length` becomes a candidate for mangling and it will be mangled throughout
the code, regardless if it's being used as part of your own objects or
accessing an array's length. To avoid that, you can use `--reserved-file`
to pass a filename that should contain the names to be excluded from
mangling. This file can be used both for excluding variable names and
property names. It could look like this, for example:
```js
{
"vars": [ "define", "require", ... ],
"props": [ "length", "prototype", ... ]
}
```
`--reserved-file` can be an array of file names (either a single
comma-separated argument, or you can pass multiple `--reserved-file`
arguments) — in this case it will exclude names from all those files.
A default exclusion file is provided in `tools/domprops.json` which should A default exclusion file is provided in `tools/domprops.json` which should
cover most standard JS and DOM properties defined in various browsers. Pass cover most standard JS and DOM properties defined in various browsers. Pass
`--reserve-domprops` to read that in. `--mangle-props domprops` to disable this feature.
You can also use a regular expression to define which property names should be You can also use a regular expression to define which property names should be
mangled. For example, `--mangle-regex="/^_/"` will only mangle property names mangled. For example, `--mangle-props regex=/^_/` will only mangle property
that start with an underscore. names that start with an underscore.
When you compress multiple files using this option, in order for them to When you compress multiple files using this option, in order for them to
work together in the end we need to ensure somehow that one property gets work together in the end we need to ensure somehow that one property gets
mangled to the same name in all of them. For this, pass `--name-cache mangled to the same name in all of them. For this, pass `--name-cache filename.json`
filename.json` and UglifyJS will maintain these mappings in a file which can and UglifyJS will maintain these mappings in a file which can then be reused.
then be reused. It should be initially empty. Example: It should be initially empty. Example:
``` ```
rm -f /tmp/cache.json # start fresh rm -f /tmp/cache.json # start fresh
@@ -294,26 +258,26 @@ of mangled property names.
Using the name cache is not necessary if you compress all your files in a Using the name cache is not necessary if you compress all your files in a
single call to UglifyJS. single call to UglifyJS.
#### Mangling unquoted names (`--mangle-props=unquoted` or `--mangle-props=2`) #### Mangling unquoted names (`--mangle-props keep_quoted`)
Using quoted property name (`o["foo"]`) reserves the property name (`foo`) Using quoted property name (`o["foo"]`) reserves the property name (`foo`)
so that it is not mangled throughout the entire script even when used in an so that it is not mangled throughout the entire script even when used in an
unquoted style (`o.foo`). Example: unquoted style (`o.foo`). Example:
``` ```
$ echo 'var o={"foo":1, bar:3}; o.foo += o.bar; console.log(o.foo);' | uglifyjs --mangle-props=2 -mc $ echo 'var o={"foo":1, bar:3}; o.foo += o.bar; console.log(o.foo);' | uglifyjs --mangle-props keep_quoted -mc
var o={"foo":1,a:3};o.foo+=o.a,console.log(o.foo); var o={foo:1,a:3};o.foo+=o.a,console.log(o.foo);
``` ```
#### Debugging property name mangling #### Debugging property name mangling
You can also pass `--mangle-props-debug` in order to mangle property names You can also pass `--mangle-props debug` in order to mangle property names
without completely obscuring them. For example the property `o.foo` without completely obscuring them. For example the property `o.foo`
would mangle to `o._$foo$_` with this option. This allows property mangling would mangle to `o._$foo$_` with this option. This allows property mangling
of a large codebase while still being able to debug the code and identify of a large codebase while still being able to debug the code and identify
where mangling is breaking things. where mangling is breaking things.
You can also pass a custom suffix using `--mangle-props-debug=XYZ`. This would then You can also pass a custom suffix using `--mangle-props debug=XYZ`. This would then
mangle `o.foo` to `o._$foo$XYZ_`. You can change this each time you compile a mangle `o.foo` to `o._$foo$XYZ_`. You can change this each time you compile a
script to identify how a property got mangled. One technique is to pass a script to identify how a property got mangled. One technique is to pass a
random number on every compile to simulate mangling changing with different random number on every compile to simulate mangling changing with different
@@ -411,6 +375,8 @@ to set `true`; it's effectively a shortcut for `foo=true`).
- `pure_getters` -- the default is `false`. If you pass `true` for - `pure_getters` -- the default is `false`. If you pass `true` for
this, UglifyJS will assume that object property access this, UglifyJS will assume that object property access
(e.g. `foo.bar` or `foo["bar"]`) doesn't have any side effects. (e.g. `foo.bar` or `foo["bar"]`) doesn't have any side effects.
Specify `"strict"` to treat `foo.bar` as side-effect-free only when
`foo` is certain to not throw, i.e. not `null` or `undefined`.
- `pure_funcs` -- default `null`. You can pass an array of names and - `pure_funcs` -- default `null`. You can pass an array of names and
UglifyJS will assume that those functions do not produce side UglifyJS will assume that those functions do not produce side
@@ -499,8 +465,6 @@ code as usual. The build will contain the `const` declarations if you use
them. If you are targeting < ES6 environments which does not support `const`, them. If you are targeting < ES6 environments which does not support `const`,
using `var` with `reduce_vars` (enabled by default) should suffice. using `var` with `reduce_vars` (enabled by default) should suffice.
<a name="codegen-options"></a>
#### Conditional compilation, API #### Conditional compilation, API
You can also use conditional compilation via the programmatic API. With the difference that the You can also use conditional compilation via the programmatic API. With the difference that the
property name is `global_defs` and is a compressor property: property name is `global_defs` and is a compressor property:
@@ -568,8 +532,8 @@ You can pass `--comments` to retain certain comments in the output. By
default it will keep JSDoc-style comments that contain "@preserve", default it will keep JSDoc-style comments that contain "@preserve",
"@license" or "@cc_on" (conditional compilation for IE). You can pass "@license" or "@cc_on" (conditional compilation for IE). You can pass
`--comments all` to keep all the comments, or a valid JavaScript regexp to `--comments all` to keep all the comments, or a valid JavaScript regexp to
keep only comments that match this regexp. For example `--comments keep only comments that match this regexp. For example `--comments /^!/`
'/foo|bar/'` will keep only comments that contain "foo" or "bar". will keep comments like `/*! Copyright Notice */`.
Note, however, that there might be situations where comments are lost. For Note, however, that there might be situations where comments are lost. For
example: example:
@@ -592,7 +556,7 @@ needs to be kept in the output) are comments attached to toplevel nodes.
## Support for the SpiderMonkey AST ## Support for the SpiderMonkey AST
UglifyJS2 has its own abstract syntax tree format; for UglifyJS has its own abstract syntax tree format; for
[practical reasons](http://lisperator.net/blog/uglifyjs-why-not-switching-to-spidermonkey-ast/) [practical reasons](http://lisperator.net/blog/uglifyjs-why-not-switching-to-spidermonkey-ast/)
we can't easily change to using the SpiderMonkey AST internally. However, we can't easily change to using the SpiderMonkey AST internally. However,
UglifyJS now has a converter which can import a SpiderMonkey AST. UglifyJS now has a converter which can import a SpiderMonkey AST.
@@ -602,54 +566,22 @@ SpiderMonkey AST. It has a small CLI utility that parses one file and dumps
the AST in JSON on the standard output. To use UglifyJS to mangle and the AST in JSON on the standard output. To use UglifyJS to mangle and
compress that: compress that:
acorn file.js | uglifyjs --spidermonkey -m -c acorn file.js | uglifyjs -p spidermonkey -m -c
The `--spidermonkey` option tells UglifyJS that all input files are not The `-p spidermonkey` option tells UglifyJS that all input files are not
JavaScript, but JS code described in SpiderMonkey AST in JSON. Therefore we JavaScript, but JS code described in SpiderMonkey AST in JSON. Therefore we
don't use our own parser in this case, but just transform that AST into our don't use our own parser in this case, but just transform that AST into our
internal AST. internal AST.
### Use Acorn for parsing ### Use Acorn for parsing
More for fun, I added the `--acorn` option which will use Acorn to do all More for fun, I added the `-p acorn` option which will use Acorn to do all
the parsing. If you pass this option, UglifyJS will `require("acorn")`. the parsing. If you pass this option, UglifyJS will `require("acorn")`.
Acorn is really fast (e.g. 250ms instead of 380ms on some 650K code), but Acorn is really fast (e.g. 250ms instead of 380ms on some 650K code), but
converting the SpiderMonkey tree that Acorn produces takes another 150ms so converting the SpiderMonkey tree that Acorn produces takes another 150ms so
in total it's a bit more than just using UglifyJS's own parser. in total it's a bit more than just using UglifyJS's own parser.
### Using UglifyJS to transform SpiderMonkey AST
Now you can use UglifyJS as any other intermediate tool for transforming
JavaScript ASTs in SpiderMonkey format.
Example:
```javascript
function uglify(ast, options, mangle) {
// Conversion from SpiderMonkey AST to internal format
var uAST = UglifyJS.AST_Node.from_mozilla_ast(ast);
// Compression
uAST.figure_out_scope();
uAST = UglifyJS.Compressor(options).compress(uAST);
// Mangling (optional)
if (mangle) {
uAST.figure_out_scope();
uAST.compute_char_frequency();
uAST.mangle_names();
}
// Back-conversion to SpiderMonkey AST
return uAST.to_mozilla_ast();
}
```
Check out
[original blog post](http://rreverser.com/using-mozilla-ast-with-uglifyjs/)
for details.
API Reference API Reference
------------- -------------
@@ -659,107 +591,72 @@ like this:
var UglifyJS = require("uglify-js"); var UglifyJS = require("uglify-js");
``` ```
It exports a lot of names, but I'll discuss here the basics that are needed There is a single toplevel function, `minify(files, options)`, which will
for parsing, mangling and compressing a piece of code. The sequence is (1) performs all the steps in a configurable manner.
parse, (2) compress, (3) mangle, (4) generate output code.
### The simple way
There's a single toplevel function which combines all the steps. If you
don't need additional customization, you might want to go with `minify`.
Example: Example:
```javascript ```javascript
var result = UglifyJS.minify("/path/to/file.js"); var result = UglifyJS.minify("var b = function() {};");
console.log(result.code); // minified output console.log(result.code); // minified output
// if you need to pass code instead of file name
var result = UglifyJS.minify("var b = function () {};", {fromString: true});
``` ```
You can also compress multiple files: You can also compress multiple files:
```javascript ```javascript
var result = UglifyJS.minify([ "file1.js", "file2.js", "file3.js" ]); var result = UglifyJS.minify({
"file1.js": "var a = function() {};",
"file2.js": "var b = function() {};"
});
console.log(result.code); console.log(result.code);
``` ```
To generate a source map: To generate a source map:
```javascript ```javascript
var result = UglifyJS.minify([ "file1.js", "file2.js", "file3.js" ], { var result = UglifyJS.minify({"file1.js": "var a = function() {};"}, {
outSourceMap: "out.js.map" sourceMap: {
filename: "out.js",
url: "out.js.map"
}
}); });
console.log(result.code); // minified output console.log(result.code); // minified output
console.log(result.map); console.log(result.map); // source map
```
To generate a source map with the fromString option, you can also use an object:
```javascript
var result = UglifyJS.minify({"file1.js": "var a = function () {};"}, {
outSourceMap: "out.js.map",
outFileName: "out.js",
fromString: true
});
``` ```
Note that the source map is not saved in a file, it's just returned in Note that the source map is not saved in a file, it's just returned in
`result.map`. The value passed for `outSourceMap` is only used to set `result.map`. The value passed for `sourceMap.url` is only used to set
`//# sourceMappingURL=out.js.map` in `result.code`. The value of `//# sourceMappingURL=out.js.map` in `result.code`. The value of
`outFileName` is only used to set `file` attribute in source map file. `filename` is only used to set `file` attribute (see [the spec][sm-spec])
in source map file.
The `file` attribute in the source map (see [the spec][sm-spec]) will You can set option `sourceMap.url` to be `"inline"` and source map will
use `outFileName` firstly, if it's falsy, then will be deduced from
`outSourceMap` (by removing `'.map'`).
You can set option `sourceMapInline` to be `true` and source map will
be appended to code. be appended to code.
You can also specify sourceRoot property to be included in source map: You can also specify sourceRoot property to be included in source map:
```javascript ```javascript
var result = UglifyJS.minify([ "file1.js", "file2.js", "file3.js" ], { var result = UglifyJS.minify({"file1.js": "var a = function() {};"}, {
outSourceMap: "out.js.map", sourceMap: {
sourceRoot: "http://example.com/src" root: "http://example.com/src",
url: "out.js.map"
}
}); });
``` ```
If you're compressing compiled JavaScript and have a source map for it, you If you're compressing compiled JavaScript and have a source map for it, you
can use the `inSourceMap` argument: can use `sourceMap.content`:
```javascript ```javascript
var result = UglifyJS.minify("compiled.js", { var result = UglifyJS.minify({"compiled.js": "compiled code"}, {
inSourceMap: "compiled.js.map", sourceMap: {
outSourceMap: "minified.js.map" content: "content from compiled.js.map",
url: "minified.js.map"
}
}); });
// same as before, it returns `code` and `map` // same as before, it returns `code` and `map`
``` ```
If your input source map is not in a file, you can pass it in as an object If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.url`.
using the `inSourceMap` argument:
```javascript
var result = UglifyJS.minify("compiled.js", {
inSourceMap: JSON.parse(my_source_map_string),
outSourceMap: "minified.js.map"
});
```
The `inSourceMap` is only used if you also request `outSourceMap` (it makes
no sense otherwise).
To set the source map url, use the `sourceMapUrl` option.
If you're using the X-SourceMap header instead, you can just set the `sourceMapUrl` option to false.
Defaults to outSourceMap:
```javascript
var result = UglifyJS.minify([ "file1.js" ], {
outSourceMap: "out.js.map",
sourceMapUrl: "localhost/out.js.map"
});
```
Other options: Other options:
- `warnings` (default `false`) — pass `true` to display compressor warnings. - `warnings` (default `false`) — pass `true` to display compressor warnings.
- `fromString` (default `false`) — if you pass `true` then you can pass
JavaScript source code, rather than file names.
- `mangle` (default `true`) — pass `false` to skip mangling names, or pass - `mangle` (default `true`) — pass `false` to skip mangling names, or pass
an object to specify mangling options (see below). an object to specify mangling options (see below).
@@ -767,18 +664,18 @@ Other options:
mangle property options. mangle property options.
- `output` (default `null`) — pass an object if you wish to specify - `output` (default `null`) — pass an object if you wish to specify
additional [output options][codegen]. The defaults are optimized additional [output options](#beautifier-options). The defaults are optimized
for best compression. for best compression.
- `compress` (default `{}`) — pass `false` to skip compressing entirely. - `compress` (default `{}`) — pass `false` to skip compressing entirely.
Pass an object to specify custom [compressor options][compressor]. Pass an object to specify custom [compressor options](#compressor-options).
- `parse` (default {}) — pass an object if you wish to specify some - `parse` (default {}) — pass an object if you wish to specify some
additional [parser options][parser]. (not all options available... see below) additional [parser options](#the-parser).
##### mangle ##### mangle
- `except` - 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).
@@ -803,183 +700,22 @@ Other options:
UglifyJS.minify("tst.js").code; UglifyJS.minify("tst.js").code;
// 'function funcName(a,n){}var globalVar;' // 'function funcName(a,n){}var globalVar;'
UglifyJS.minify("tst.js", { mangle: { except: ['firstLongName'] } }).code; UglifyJS.minify("tst.js", { 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("tst.js", { mangle: { toplevel: true } }).code;
// 'function n(n,a){}var a;' // 'function n(n,a){}var a;'
``` ```
##### mangleProperties options ##### mangle.properties options
- `regex` — Pass a RegExp to only mangle certain names (maps to the `--mangle-regex` CLI arguments option) - `regex` — Pass a RegExp to only mangle certain names
- `ignore_quoted` Only mangle unquoted property names (maps to the `--mangle-props 2` CLI arguments option) - `keep_quoted` Only mangle unquoted property names
- `debug` Mangle names with the original name still present (maps to the `--mangle-props-debug` CLI arguments option). Defaults to `false`. Pass an empty string to enable, or a non-empty string to set the suffix. - `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.
We could add more options to `UglifyJS.minify` — if you need additional
functionality please suggest!
### The hard way
Following there's more detailed API info, in case the `minify` function is
too simple for your needs.
#### The parser
```javascript
var toplevel_ast = UglifyJS.parse(code, options);
```
`options` is optional and if present it must be an object. The following
properties are available:
- `strict` — disable automatic semicolon insertion and support for trailing
comma in arrays and objects
- `bare_returns` — Allow return outside of functions. (maps to the
`--bare-returns` CLI arguments option and available to `minify` `parse`
other options object)
- `filename` — the name of the file where this code is coming from
- `toplevel` — a `toplevel` node (as returned by a previous invocation of
`parse`)
The last two options are useful when you'd like to minify multiple files and
get a single file as the output and a proper source map. Our CLI tool does
something like this:
```javascript
var toplevel = null;
files.forEach(function(file){
var code = fs.readFileSync(file, "utf8");
toplevel = UglifyJS.parse(code, {
filename: file,
toplevel: toplevel
});
});
```
After this, we have in `toplevel` a big AST containing all our files, with
each token having proper information about where it came from.
#### Scope information
UglifyJS contains a scope analyzer that you need to call manually before
compressing or mangling. Basically it augments various nodes in the AST
with information about where is a name defined, how many times is a name
referenced, if it is a global or not, if a function is using `eval` or the
`with` statement etc. I will discuss this some place else, for now what's
important to know is that you need to call the following before doing
anything with the tree:
```javascript
toplevel.figure_out_scope()
```
#### Compression
Like this:
```javascript
var compressor = UglifyJS.Compressor(options);
var compressed_ast = compressor.compress(toplevel);
```
The `options` can be missing. Available options are discussed above in
“Compressor options”. Defaults should lead to best compression in most
scripts.
The compressor is destructive, so don't rely that `toplevel` remains the
original tree.
#### Mangling
After compression it is a good idea to call again `figure_out_scope` (since
the compressor might drop unused variables / unreachable code and this might
change the number of identifiers or their position). Optionally, you can
call a trick that helps after Gzip (counting character frequency in
non-mangleable words). Example:
```javascript
compressed_ast.figure_out_scope();
compressed_ast.compute_char_frequency();
compressed_ast.mangle_names();
```
#### Generating output
AST nodes have a `print` method that takes an output stream. Essentially,
to generate code you do this:
```javascript
var stream = UglifyJS.OutputStream(options);
compressed_ast.print(stream);
var code = stream.toString(); // this is your minified code
```
or, for a shortcut you can do:
```javascript
var code = compressed_ast.print_to_string(options);
```
As usual, `options` is optional. The output stream accepts a lot of options,
most of them documented above in section “Beautifier options”. The two
which we care about here are `source_map` and `comments`.
#### Keeping comments in the output
In order to keep certain comments in the output you need to pass the
`comments` option. Pass a RegExp (as string starting and closing with `/`
or pass a RegExp object), a boolean or a function. Stringified options
`all` and `some` can be passed too, where `some` behaves like it's cli
equivalent `--comments` without passing a value. If you pass a RegExp,
only those comments whose body matches the RegExp will be kept. Note that body
means without the initial `//` or `/*`. If you pass a function, it will be
called for every comment in the tree and will receive two arguments: the
node that the comment is attached to, and the comment token itself.
The comment token has these properties:
- `type`: "comment1" for single-line comments or "comment2" for multi-line
comments
- `value`: the comment body
- `pos` and `endpos`: the start/end positions (zero-based indexes) in the
original code where this comment appears
- `line` and `col`: the line and column where this comment appears in the
original code
- `file` — the file name of the original file
- `nlb` — true if there was a newline before this comment in the original
code, or if this comment contains a newline.
Your function should return `true` to keep the comment, or a falsy value
otherwise.
#### Generating a source mapping
You need to pass the `source_map` argument when calling `print`. It needs
to be a `SourceMap` object (which is a thin wrapper on top of the
[source-map][source-map] library).
Example:
```javascript
var source_map = UglifyJS.SourceMap(source_map_options);
var stream = UglifyJS.OutputStream({
...
source_map: source_map
});
compressed_ast.print(stream);
var code = stream.toString();
var map = source_map.toString(); // json output for your source map
```
The `source_map_options` (optional) can contain the following properties:
- `file`: the name of the JavaScript output file that this mapping refers to
- `root`: the `sourceRoot` property (see the [spec][sm-spec])
- `orig`: the "original source map", handy when you compress generated JS
and want to map the minified output back to the original code where it
came from. It can be simply a string in JSON, or a JSON object containing
the original source map.
[acorn]: https://github.com/ternjs/acorn [acorn]: https://github.com/ternjs/acorn
[source-map]: https://github.com/mozilla/source-map [sm-spec]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k
[sm-spec]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit
[codegen]: http://lisperator.net/uglifyjs/codegen
[compressor]: http://lisperator.net/uglifyjs/compress
[parser]: http://lisperator.net/uglifyjs/parser
#### Harmony #### Harmony

View File

@@ -1,77 +0,0 @@
#! /usr/bin/env node
"use strict";
var U2 = require("../tools/node");
var fs = require("fs");
var yargs = require("yargs");
var ARGS = yargs
.describe("o", "Output file")
.argv;
var files = ARGS._.slice();
var output = {
vars: {},
props: {}
};
if (ARGS.o) try {
output = JSON.parse(fs.readFileSync(ARGS.o, "utf8"));
} catch(ex) {}
files.forEach(getProps);
if (ARGS.o) {
fs.writeFileSync(ARGS.o, JSON.stringify(output, null, 2), "utf8");
} else {
console.log("%s", JSON.stringify(output, null, 2));
}
function getProps(filename) {
var code = fs.readFileSync(filename, "utf8");
var ast = U2.parse(code);
ast.walk(new U2.TreeWalker(function(node){
if (node instanceof U2.AST_ObjectKeyVal) {
add(node.key);
}
else if (node instanceof U2.AST_ObjectProperty) {
add(node.key.name);
}
else if (node instanceof U2.AST_Dot) {
add(node.property);
}
else if (node instanceof U2.AST_Sub) {
addStrings(node.property);
}
}));
function addStrings(node) {
var out = {};
try {
(function walk(node){
node.walk(new U2.TreeWalker(function(node){
if (node instanceof U2.AST_Seq) {
walk(node.cdr);
return true;
}
if (node instanceof U2.AST_String) {
add(node.value);
return true;
}
if (node instanceof U2.AST_Conditional) {
walk(node.consequent);
walk(node.alternative);
return true;
}
throw out;
}));
})(node);
} catch(ex) {
if (ex !== out) throw ex;
}
}
function add(name) {
output.props[name] = true;
}
}

View File

@@ -3,633 +3,382 @@
"use strict"; "use strict";
var UglifyJS = require("../tools/node"); // workaround for tty output truncation upon process.exit()
var sys = require("util"); [process.stdout, process.stderr].forEach(function(stream){
var yargs = require("yargs"); if (stream._handle && stream._handle.setBlocking)
var fs = require("fs"); stream._handle.setBlocking(true);
var path = require("path");
var acorn;
var screw_ie8 = true;
var ARGS = yargs
.usage("$0 input1.js [input2.js ...] [options]\n\
Use a single dash to read input from the standard input.\
\n\n\
NOTE: by default there is no mangling/compression.\n\
Without [options] it will simply parse input files and dump the AST\n\
with whitespace and comments discarded. To achieve compression and\n\
mangling you need to use `-c` and `-m`.\
")
.describe("source-map", "Specify an output file where to generate source map.")
.describe("source-map-root", "The path to the original source to be included in the source map.")
.describe("source-map-url", "The path to the source map to be added in //# sourceMappingURL. Defaults to the value passed with --source-map.")
.describe("source-map-inline", "Write base64-encoded source map to the end of js output. Disabled by default")
.describe("source-map-include-sources", "Pass this flag if you want to include the content of source files in the source map as sourcesContent property.")
.describe("in-source-map", "Input source map, useful if you're compressing JS that was generated from some other original code.")
.describe("screw-ie8", "Do not support Internet Explorer 6/7/8. This flag is enabled by default.")
.describe("support-ie8", "Support non-standard Internet Explorer 6/7/8 javascript.")
.describe("expr", "Parse a single expression, rather than a program (for parsing JSON)")
.describe("p", "Skip prefix for original filenames that appear in source maps. \
For example -p 3 will drop 3 directories from file names and ensure they are relative paths. \
You can also specify -p relative, which will make UglifyJS figure out itself the relative paths between original sources, \
the source map and the output file.")
.describe("o", "Output file (default STDOUT).")
.describe("b", "Beautify output/specify output options.")
.describe("m", "Mangle names/pass mangler options.")
.describe("r", "Reserved names to exclude from mangling.")
.describe("c", "Enable compressor/pass compressor options. \
Pass options like -c hoist_vars=false,if_return=false. \
Use -c with no argument to use the default compression options.")
.describe("d", "Global definitions")
.describe("e", "Embed everything in a big function, with a configurable parameter/argument list.")
.describe("comments", "Preserve copyright comments in the output. \
By default this works like Google Closure, keeping JSDoc-style comments that contain \"@license\" or \"@preserve\". \
You can optionally pass one of the following arguments to this flag:\n\
- \"all\" to keep all comments\n\
- a valid JS RegExp like `/foo/`or `/^!/` to keep only matching comments.\n\
\
Note that currently not *all* comments can be kept when compression is on, \
because of dead code removal or cascading statements into sequences.")
.describe("preamble", "Preamble to prepend to the output. You can use this to insert a \
comment, for example for licensing information. This will not be \
parsed, but the source map will adjust for its presence.")
.describe("stats", "Display operations run time on STDERR.")
.describe("acorn", "Use Acorn for parsing.")
.describe("spidermonkey", "Assume input files are SpiderMonkey AST format (as JSON).")
.describe("self", "Build itself (UglifyJS2) as a library (implies --wrap=UglifyJS --export-all)")
.describe("wrap", "Embed everything in a big function, making the “exports” and “global” variables available. \
You need to pass an argument to this option to specify the name that your module will take when included in, say, a browser.")
.describe("export-all", "Only used when --wrap, this tells UglifyJS to add code to automatically export all globals.")
.describe("lint", "Display some scope warnings")
.describe("v", "Verbose")
.describe("V", "Print version number and exit.")
.describe("noerr", "Don't throw an error for unknown options in -c, -b or -m.")
.describe("bare-returns", "Allow return outside of functions. Useful when minifying CommonJS modules.")
.describe("keep-fnames", "Do not mangle/drop function names. Useful for code relying on Function.prototype.name.")
.describe("quotes", "Quote style (0 - auto, 1 - single, 2 - double, 3 - original)")
.describe("reserved-file", "File containing reserved names")
.describe("reserve-domprops", "Make (most?) DOM properties reserved for --mangle-props")
.describe("mangle-props", "Mangle property names (0 - disabled, 1 - mangle all properties, 2 - mangle unquoted properies)")
.describe("mangle-regex", "Only mangle property names matching the regex")
.describe("name-cache", "File to hold mangled names mappings")
.describe("pure-funcs", "List of functions that can be safely removed if their return value is not used")
.describe("dump-spidermonkey-ast", "Dump SpiderMonkey AST to stdout.")
.describe("wrap-iife", "Wrap IIFEs in parenthesis. Note: this disables the negate_iife compression option")
.alias("p", "prefix")
.alias("o", "output")
.alias("v", "verbose")
.alias("b", "beautify")
.alias("m", "mangle")
.alias("c", "compress")
.alias("d", "define")
.alias("r", "reserved")
.alias("V", "version")
.alias("e", "enclose")
.alias("q", "quotes")
.string("source-map")
.string("source-map-root")
.string("source-map-url")
.string("b")
.string("beautify")
.string("m")
.string("mangle")
.string("mangle-props-debug")
.string("c")
.string("compress")
.string("d")
.string("define")
.string("e")
.string("enclose")
.string("comments")
.string("wrap")
.string("p")
.string("prefix")
.string("name-cache")
.array("reserved-file")
.array("pure-funcs")
.boolean("expr")
.boolean("source-map-inline")
.boolean("source-map-include-sources")
.boolean("screw-ie8")
.boolean("support-ie8")
.boolean("export-all")
.boolean("self")
.boolean("v")
.boolean("verbose")
.boolean("stats")
.boolean("acorn")
.boolean("spidermonkey")
.boolean("dump-spidermonkey-ast")
.boolean("lint")
.boolean("V")
.boolean("version")
.boolean("noerr")
.boolean("bare-returns")
.boolean("keep-fnames")
.boolean("reserve-domprops")
.boolean("wrap-iife")
.wrap(80)
.argv
;
normalize(ARGS);
if (ARGS.noerr) {
UglifyJS.DefaultsError.croak = function(msg, defs) {
print_error("WARN: " + msg);
};
}
if (ARGS.version || ARGS.V) {
var json = require("../package.json");
print(json.name + ' ' + json.version);
process.exit(0);
}
if (ARGS.ast_help) {
var desc = UglifyJS.describe_ast();
print(typeof desc == "string" ? desc : JSON.stringify(desc, null, 2));
process.exit(0);
}
if (ARGS.h || ARGS.help) {
print(yargs.help());
process.exit(0);
}
if (ARGS.acorn) {
acorn = require("acorn");
}
var COMPRESS = getOptions("c", true);
var MANGLE = getOptions("m", true);
var BEAUTIFY = getOptions("b", true);
var RESERVED = null;
if (ARGS.reserved_file) ARGS.reserved_file.forEach(function(filename){
RESERVED = UglifyJS.readReservedFile(filename, RESERVED);
}); });
if (ARGS.reserve_domprops) { var fs = require("fs");
RESERVED = UglifyJS.readDefaultReservedFile(RESERVED); var info = require("../package.json");
} var path = require("path");
var program = require("commander");
var UglifyJS = require("../tools/node");
if (ARGS.d) { var skip_keys = [ "cname", "enclosed", "parent_scope", "scope", "thedef", "uses_eval", "uses_with" ];
if (COMPRESS) COMPRESS.global_defs = getOptions("d"); var files = {};
} var options = {
compress: false,
if (ARGS.pure_funcs) { mangle: false
if (COMPRESS) COMPRESS.pure_funcs = ARGS.pure_funcs;
}
if (ARGS.r) {
if (MANGLE) MANGLE.except = ARGS.r.replace(/^\s+|\s+$/g).split(/\s*,+\s*/);
}
if (RESERVED && MANGLE) {
if (!MANGLE.except) MANGLE.except = RESERVED.vars;
else MANGLE.except = MANGLE.except.concat(RESERVED.vars);
}
function readNameCache(key) {
return UglifyJS.readNameCache(ARGS.name_cache, key);
}
function writeNameCache(key, cache) {
return UglifyJS.writeNameCache(ARGS.name_cache, key, cache);
}
function extractRegex(str) {
if (/^\/.*\/[a-zA-Z]*$/.test(str)) {
var regex_pos = str.lastIndexOf("/");
return new RegExp(str.substr(1, regex_pos - 1), str.substr(regex_pos + 1));
} else {
throw new Error("Invalid regular expression: " + str);
}
}
if (ARGS.quotes === true) {
ARGS.quotes = 3;
}
if (ARGS.mangle_props === true) {
ARGS.mangle_props = 1;
} else if (ARGS.mangle_props === "unquoted") {
ARGS.mangle_props = 2;
}
var OUTPUT_OPTIONS = {
beautify : BEAUTIFY ? true : false,
max_line_len : 32000,
preamble : ARGS.preamble || null,
quote_style : ARGS.quotes != null ? ARGS.quotes : 0,
}; };
program._name = info.name;
if (ARGS.mangle_props == 2) { program.version(info.version);
OUTPUT_OPTIONS.keep_quoted_props = true; program.parseArgv = program.parse;
if (COMPRESS && !("properties" in COMPRESS)) program.parse = undefined;
COMPRESS.properties = false; program.option("-p, --parse <options>", "Specify parser options.", parse_js("parse", true));
program.option("-c, --compress [options]", "Enable compressor/specify compressor options.", parse_js("compress", true));
program.option("-m, --mangle [options]", "Mangle names/specify mangler options.", parse_js("mangle", true));
program.option("--mangle-props [options]", "Mangle properties/specify mangler options.", parse_js("mangle-props", true));
program.option("-b, --beautify [options]", "Beautify output/specify output options.", parse_js("beautify", true));
program.option("-o, --output <file>", "Output file (default STDOUT).");
program.option("--comments [filter]", "Preserve copyright comments in the output.");
program.option("--config-file <file>", "Read minify() options from JSON file.");
program.option("-d, --define <expr>[=value]", "Global definitions.", parse_js("define"));
program.option("--ie8", "Support non-standard Internet Explorer 8.");
program.option("--keep-fnames", "Do not mangle/drop function names. Useful for code relying on Function.prototype.name.");
program.option("--name-cache <file>", "File to hold mangled name mappings.");
program.option("--self", "Build UglifyJS as a library (implies --wrap UglifyJS)");
program.option("--source-map [options]", "Enable source map/specify source map options.", parse_source_map());
program.option("--stats", "Display operations run time on STDERR.")
program.option("--toplevel", "Compress and/or mangle variables in toplevel scope.");
program.option("--verbose", "Print diagnostic messages.");
program.option("--warn", "Print warning messages.");
program.option("--wrap <name>", "Embed everything as a function with “exports” corresponding to “name” globally.");
program.arguments("[files...]").parseArgv(process.argv);
if (program.configFile) {
options = JSON.parse(read_file(program.configFile));
} }
if (!program.output && program.sourceMap && program.sourceMap.url != "inline") {
if (ARGS.support_ie8 === true && ARGS.screw_ie8 !== true) { fatal("ERROR: cannot write source map to STDOUT");
screw_ie8 = false;
} }
[
if (COMPRESS) COMPRESS.screw_ie8 = screw_ie8; "compress",
if (MANGLE) MANGLE.screw_ie8 = screw_ie8; "ie8",
OUTPUT_OPTIONS.screw_ie8 = screw_ie8; "mangle",
"sourceMap",
if (ARGS.keep_fnames) { "toplevel",
if (COMPRESS) COMPRESS.keep_fnames = true; "wrap"
if (MANGLE) MANGLE.keep_fnames = true; ].forEach(function(name) {
if (name in program) {
options[name] = program[name];
}
});
if (program.beautify) {
options.output = typeof program.beautify == "object" ? program.beautify : {};
if (!("beautify" in options.output)) {
options.output.beautify = true;
}
} }
if (program.comments) {
if (ARGS.wrap_iife) { if (typeof options.output != "object") options.output = {};
if (COMPRESS) COMPRESS.negate_iife = false; options.output.comments = typeof program.comments == "string" ? program.comments : "some";
OUTPUT_OPTIONS.wrap_iife = true;
} }
if (program.define) {
if (BEAUTIFY) if (typeof options.compress != "object") options.compress = {};
UglifyJS.merge(OUTPUT_OPTIONS, BEAUTIFY); if (typeof options.compress.global_defs != "object") options.compress.global_defs = {};
for (var expr in program.define) {
if (ARGS.comments === "") { options.compress.global_defs[expr] = program.define[expr];
OUTPUT_OPTIONS.comments = "some"; }
}
if (program.keepFnames) {
options.keep_fnames = true;
}
if (program.mangleProps) {
if (program.mangleProps.domprops) {
delete program.mangleProps.domprops;
} else {
if (typeof program.mangleProps != "object") program.mangleProps = {};
if (!Array.isArray(program.mangleProps.reserved)) program.mangleProps.reserved = [];
require("../tools/domprops").forEach(function(name) {
UglifyJS._push_uniq(program.mangleProps.reserved, name);
});
}
if (typeof options.mangle != "object") options.mangle = {};
options.mangle.properties = program.mangleProps;
}
var cache;
if (program.nameCache) {
cache = JSON.parse(read_file(program.nameCache, "{}"));
if (options.mangle) {
if (typeof options.mangle != "object") options.mangle = {};
options.mangle.cache = to_cache("vars");
if (options.mangle.properties) {
if (typeof options.mangle.properties != "object") options.mangle.properties = {};
options.mangle.properties.cache = to_cache("props");
}
}
}
if (program.output == "ast") {
options.output = {
ast: true,
code: false
};
}
if (program.parse) {
if (program.parse.acorn || program.parse.spidermonkey) {
if (program.sourceMap) fatal("ERROR: inline source map only works with built-in parser");
} else {
options.parse = program.parse;
}
}
var convert_path = function(name) {
return name;
};
if (typeof program.sourceMap == "object" && "base" in program.sourceMap) {
convert_path = function() {
var base = program.sourceMap.base;
delete options.sourceMap.base;
return function(name) {
return path.relative(base, name);
};
}();
}
if (program.verbose) {
options.warnings = "verbose";
} else if (program.warn) {
options.warnings = true;
}
if (program.self) {
if (program.args.length) {
console.error("WARN: Ignoring input files since --self was passed");
}
if (!options.wrap) options.wrap = "UglifyJS";
simple_glob(UglifyJS.FILES).forEach(function(name) {
files[convert_path(name)] = read_file(name);
});
run();
} else if (program.args.length) {
simple_glob(program.args).forEach(function(name) {
files[convert_path(name)] = read_file(name);
});
run();
} else { } else {
OUTPUT_OPTIONS.comments = ARGS.comments; var chunks = [];
process.stdin.setEncoding("utf8");
process.stdin.on("data", function(chunk) {
chunks.push(chunk);
}).on("end", function() {
files = [ chunks.join("") ];
run();
});
process.stdin.resume();
} }
var files = ARGS._.slice(); function convert_ast(fn) {
return UglifyJS.AST_Node.from_mozilla_ast(Object.keys(files).reduce(fn, null));
if (process.platform === "win32")
files = UglifyJS.simple_glob(files);
if (ARGS.self) {
if (files.length > 0) {
print_error("WARN: Ignoring input files since --self was passed");
}
files = UglifyJS.FILES;
if (!ARGS.wrap) ARGS.wrap = "UglifyJS";
} }
var ORIG_MAP = ARGS.in_source_map; function run() {
UglifyJS.AST_Node.warn_function = function(msg) {
if (ORIG_MAP && ORIG_MAP != "inline") { console.error("WARN:", msg);
ORIG_MAP = JSON.parse(fs.readFileSync(ORIG_MAP)); };
if (files.length == 0) { if (program.stats) program.stats = Date.now();
print_error("INFO: Using file from the input source map: " + ORIG_MAP.file); try {
files = [ ORIG_MAP.file ]; if (program.parse) {
} if (program.parse.acorn) {
} files = convert_ast(function(toplevel, name) {
return require("acorn").parse(files[name], {
if (files.length == 0) { locations: true,
files = [ "-" ]; program: toplevel,
} sourceFile: name
});
if (ORIG_MAP == "inline") { });
if (files.length > 1) { } else if (program.parse.spidermonkey) {
print_error("ERROR: Inline source map only works with singular input"); files = convert_ast(function(toplevel, name) {
process.exit(1); var obj = JSON.parse(files[name]);
} if (!toplevel) return obj;
if (ARGS.acorn || ARGS.spidermonkey) { toplevel.body = toplevel.body.concat(obj.body);
print_error("ERROR: Inline source map only works with built-in parser"); return toplevel;
process.exit(1);
}
}
if (files.indexOf("-") >= 0 && ARGS.source_map) {
print_error("ERROR: Source map doesn't work with input from STDIN");
process.exit(1);
}
if (files.filter(function(el){ return el == "-" }).length > 1) {
print_error("ERROR: Can read a single file from STDIN (two or more dashes specified)");
process.exit(1);
}
var STATS = {};
var TOPLEVEL = null;
var P_RELATIVE = ARGS.p && ARGS.p == "relative";
var SOURCES_CONTENT = {};
var index = 0;
!function cb() {
if (index == files.length) return done();
var file = files[index++];
read_whole_file(file, function (err, code) {
if (err) {
print_error("ERROR: can't read file: " + file);
process.exit(1);
}
if (ORIG_MAP == "inline") {
ORIG_MAP = read_source_map(code);
}
if (ARGS.p != null) {
if (P_RELATIVE) {
file = path.relative(path.dirname(ARGS.source_map), file).replace(/\\/g, '/');
} else {
var p = parseInt(ARGS.p, 10);
if (!isNaN(p)) {
file = file.replace(/^\/+/, "").split(/\/+/).slice(ARGS.p).join("/");
}
}
}
SOURCES_CONTENT[file] = code;
time_it("parse", function(){
if (ARGS.spidermonkey) {
var program = JSON.parse(code);
if (!TOPLEVEL) TOPLEVEL = program;
else TOPLEVEL.body = TOPLEVEL.body.concat(program.body);
}
else if (ARGS.acorn) {
TOPLEVEL = acorn.parse(code, {
locations : true,
sourceFile : file,
program : TOPLEVEL
}); });
} }
else { }
try { } catch (ex) {
TOPLEVEL = UglifyJS.parse(code, { fatal("ERROR: " + ex.message);
filename : file, }
toplevel : TOPLEVEL, var result = UglifyJS.minify(files, options);
expression : ARGS.expr, if (result.error) {
bare_returns : ARGS.bare_returns, var ex = result.error;
}); if (ex.name == "SyntaxError") {
} catch(ex) { console.error("Parse error at " + ex.filename + ":" + ex.line + "," + ex.col);
if (ex instanceof UglifyJS.JS_Parse_Error) { var col = ex.col;
print_error("Parse error at " + file + ":" + ex.line + "," + ex.col); var lines = files[ex.filename].split(/\r?\n/);
var col = ex.col; var line = lines[ex.line - 1];
var line = code.split(/\r?\n/)[ex.line - (col ? 1 : 2)]; if (!line && !col) {
if (line) { line = lines[ex.line - 2];
if (col > 40) { col = line.length;
line = line.slice(col - 40); }
col = 40; if (line) {
} if (col > 40) {
if (col) { line = line.slice(col - 40);
print_error(line.slice(0, 80)); col = 40;
print_error(line.slice(0, col).replace(/\S/g, " ") + "^");
} else {
print_error(line.slice(-40));
print_error(line.slice(-40).replace(/\S/g, " ") + "^");
}
}
print_error(ex.stack);
process.exit(1);
}
throw ex;
} }
}; console.error(line.slice(0, 80));
}); console.error(line.slice(0, col).replace(/\S/g, " ") + "^");
cb(); }
}); }
}(); if (ex.defs) {
console.error("Supported options:");
console.error(ex.defs);
}
fatal("ERROR: " + ex.message);
} else if (program.output == "ast") {
console.log(JSON.stringify(result.ast, function(key, value) {
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) {
fs.writeFileSync(program.output, result.code);
if (result.map) {
fs.writeFileSync(program.output + ".map", result.map);
}
} else {
console.log(result.code);
}
if (program.nameCache) {
fs.writeFileSync(program.nameCache, JSON.stringify(cache, function(key, value) {
return value instanceof UglifyJS.Dictionary ? value.toObject() : value;
}));
}
if (program.stats) console.error("Elapsed:", Date.now() - program.stats);
}
function done() { function fatal(message) {
var OUTPUT_FILE = ARGS.o; console.error(message);
process.exit(1);
}
var SOURCE_MAP = (ARGS.source_map || ARGS.source_map_inline) ? UglifyJS.SourceMap({ // A file glob function that only supports "*" and "?" wildcards in the basename.
file: P_RELATIVE ? path.relative(path.dirname(ARGS.source_map), OUTPUT_FILE) : OUTPUT_FILE, // Example: "foo/bar/*baz??.*.js"
root: ARGS.source_map_root || ORIG_MAP && ORIG_MAP.sourceRoot, // Argument `glob` may be a string or an array of strings.
orig: ORIG_MAP, // Returns an array of strings. Garbage in, garbage out.
}) : null; function simple_glob(glob) {
if (Array.isArray(glob)) {
OUTPUT_OPTIONS.source_map = SOURCE_MAP; return [].concat.apply([], glob.map(simple_glob));
}
if (glob.match(/\*|\?/)) {
var dir = path.dirname(glob);
try {
var entries = fs.readdirSync(dir);
} catch (ex) {}
if (entries) {
var pattern = "^" + path.basename(glob)
.replace(/[.+^$[\]\\(){}]/g, "\\$&")
.replace(/\*/g, "[^/\\\\]*")
.replace(/\?/g, "[^/\\\\]") + "$";
var mod = process.platform === "win32" ? "i" : "";
var rx = new RegExp(pattern, mod);
var results = entries.filter(function(name) {
return rx.test(name);
}).map(function(name) {
return path.join(dir, name);
});
if (results.length) return results;
}
}
return [ glob ];
}
function read_file(path, default_value) {
try { try {
var output = UglifyJS.OutputStream(OUTPUT_OPTIONS); return fs.readFileSync(path, "utf8");
var compressor = COMPRESS && UglifyJS.Compressor(COMPRESS); } catch (ex) {
} catch(ex) { if (ex.code == "ENOENT" && default_value != null) return default_value;
if (ex instanceof UglifyJS.DefaultsError) { fatal("ERROR: " + ex.message);
print_error(ex.message);
print_error("Supported options:");
print_error(sys.inspect(ex.defs));
process.exit(1);
}
} }
}
if (ARGS.acorn || ARGS.spidermonkey) time_it("convert_ast", function(){ function parse_js(flag, constants) {
TOPLEVEL = UglifyJS.AST_Node.from_mozilla_ast(TOPLEVEL); return function(value, options) {
}); options = options || {};
if (ARGS.wrap != null) {
TOPLEVEL = TOPLEVEL.wrap_commonjs(ARGS.wrap, ARGS.export_all);
}
if (ARGS.enclose != null) {
var arg_parameter_list = ARGS.enclose;
if (arg_parameter_list === true) {
arg_parameter_list = [];
}
else if (!(arg_parameter_list instanceof Array)) {
arg_parameter_list = [arg_parameter_list];
}
TOPLEVEL = TOPLEVEL.wrap_enclose(arg_parameter_list);
}
if (ARGS.mangle_props || ARGS.name_cache) (function(){
var reserved = RESERVED ? RESERVED.props : null;
var cache = readNameCache("props");
var regex;
try { try {
regex = ARGS.mangle_regex ? extractRegex(ARGS.mangle_regex) : null; UglifyJS.minify(value, {
} catch (e) { parse: {
print_error("ERROR: Invalid --mangle-regex: " + e.message); expression: true
process.exit(1); },
} compress: false,
mangle: false,
output: {
ast: true,
code: false
}
}).ast.walk(new UglifyJS.TreeWalker(function(node) {
if (node instanceof UglifyJS.AST_Assign) {
var name = node.left.print_to_string();
var value = node.right;
if (!constants) {
options[name] = value;
} else if (value instanceof UglifyJS.AST_Array) {
options[name] = value.elements.map(to_string);
} else {
options[name] = to_string(value);
}
return true;
}
if (node instanceof UglifyJS.AST_Symbol || node instanceof UglifyJS.AST_PropAccess) {
var name = node.print_to_string();
options[name] = true;
return true;
}
if (!(node instanceof UglifyJS.AST_Sequence)) throw node;
TOPLEVEL = UglifyJS.mangle_properties(TOPLEVEL, { function to_string(value) {
reserved : reserved, return value instanceof UglifyJS.AST_Constant ? value.getValue() : value.print_to_string({
cache : cache, quote_keys: true
only_cache : !ARGS.mangle_props, });
regex : regex, }
ignore_quoted : ARGS.mangle_props == 2,
debug : typeof ARGS.mangle_props_debug === "undefined" ? false : ARGS.mangle_props_debug
});
writeNameCache("props", cache);
})();
var SCOPE_IS_NEEDED = COMPRESS || MANGLE || ARGS.lint
var TL_CACHE = readNameCache("vars");
if (MANGLE) MANGLE.cache = TL_CACHE;
if (SCOPE_IS_NEEDED) {
time_it("scope", function(){
TOPLEVEL.figure_out_scope(MANGLE || { screw_ie8: screw_ie8, cache: TL_CACHE });
if (ARGS.lint) {
TOPLEVEL.scope_warnings();
}
});
}
if (COMPRESS) {
time_it("squeeze", function(){
TOPLEVEL = compressor.compress(TOPLEVEL);
});
}
if (SCOPE_IS_NEEDED) {
time_it("scope", function(){
TOPLEVEL.figure_out_scope(MANGLE || { screw_ie8: screw_ie8, cache: TL_CACHE });
if (MANGLE && !TL_CACHE) {
TOPLEVEL.compute_char_frequency(MANGLE);
}
});
}
if (MANGLE) time_it("mangle", function(){
TOPLEVEL.mangle_names(MANGLE);
});
writeNameCache("vars", TL_CACHE);
if (ARGS.source_map_include_sources) {
for (var file in SOURCES_CONTENT) {
if (SOURCES_CONTENT.hasOwnProperty(file)) {
SOURCE_MAP.get().setSourceContent(file, SOURCES_CONTENT[file]);
}
}
}
if (ARGS.dump_spidermonkey_ast) {
print(JSON.stringify(TOPLEVEL.to_mozilla_ast(), null, 2));
} else {
time_it("generate", function(){
TOPLEVEL.print(output);
});
output = output.get();
if (SOURCE_MAP) {
if (ARGS.source_map_inline) {
var base64_string = new Buffer(SOURCE_MAP.toString()).toString('base64');
output += "\n//# sourceMappingURL=data:application/json;charset=utf-8;base64," + base64_string;
} else {
fs.writeFileSync(ARGS.source_map, SOURCE_MAP, "utf8");
var source_map_url = ARGS.source_map_url || (
P_RELATIVE
? path.relative(path.dirname(OUTPUT_FILE), ARGS.source_map)
: ARGS.source_map
);
output += "\n//# sourceMappingURL=" + source_map_url;
}
}
if (OUTPUT_FILE) {
fs.writeFileSync(OUTPUT_FILE, output, "utf8");
} else {
print(output);
}
}
if (ARGS.stats) {
print_error(UglifyJS.string_template("Timing information (compressed {count} files):", {
count: files.length
}));
for (var i in STATS) if (STATS.hasOwnProperty(i)) {
print_error(UglifyJS.string_template("- {name}: {time}s", {
name: i,
time: (STATS[i] / 1000).toFixed(3)
})); }));
}
}
}
/* -----[ functions ]----- */
function normalize(o) {
for (var i in o) if (o.hasOwnProperty(i) && /-/.test(i)) {
o[i.replace(/-/g, "_")] = o[i];
delete o[i];
}
}
function getOptions(flag, constants) {
var x = ARGS[flag];
if (x == null || x === false) return null;
var ret = {};
if (x !== "") {
if (Array.isArray(x)) x = x.map(function (v) { return "(" + v + ")"; }).join(", ");
var ast;
try {
ast = UglifyJS.parse(x, { cli: true, expression: true });
} catch(ex) { } catch(ex) {
if (ex instanceof UglifyJS.JS_Parse_Error) { fatal("Error parsing arguments for '" + flag + "': " + value);
print_error("Error parsing arguments for flag `" + flag + "': " + x);
process.exit(1);
}
} }
ast.walk(new UglifyJS.TreeWalker(function(node){ return options;
if (node instanceof UglifyJS.AST_Seq) return; // descend
if (node instanceof UglifyJS.AST_Assign) {
var name = node.left.print_to_string().replace(/-/g, "_");
var value = node.right;
if (constants)
value = new Function("return (" + value.print_to_string() + ")")();
ret[name] = value;
return true; // no descend
}
if (node instanceof UglifyJS.AST_Symbol || node instanceof UglifyJS.AST_Binary) {
var name = node.print_to_string().replace(/-/g, "_");
ret[name] = true;
return true; // no descend
}
print_error(node.TYPE)
print_error("Error parsing arguments for flag `" + flag + "': " + x);
process.exit(1);
}));
} }
return ret;
} }
function read_whole_file(filename, cb) { function parse_source_map() {
if (filename == "-") { var parse = parse_js("sourceMap", true);
var chunks = []; return function(value, options) {
process.stdin.setEncoding('utf-8'); var hasContent = options && options.sourceMap && "content" in options.sourceMap;
process.stdin.on('data', function (chunk) { var settings = parse(value, options);
chunks.push(chunk); if (!hasContent && settings.content && settings.content != "inline") {
}).on('end', function () { console.error("INFO: Using input source map:", settings.content);
cb(null, chunks.join("")); settings.content = read_file(settings.content, settings.content);
}); }
process.openStdin(); return settings;
}
}
function to_cache(key) {
if (cache[key]) {
cache[key].props = UglifyJS.Dictionary.fromObject(cache[key].props);
} else { } else {
fs.readFile(filename, "utf-8", cb); cache[key] = {
cname: -1,
props: new UglifyJS.Dictionary()
};
} }
return cache[key];
} }
function read_source_map(code) { function skip_key(key) {
var match = /\n\/\/# sourceMappingURL=data:application\/json(;.*?)?;base64,(.*)/.exec(code); return skip_keys.indexOf(key) >= 0;
if (!match) {
print_error("WARN: inline source map not found");
return null;
}
return JSON.parse(new Buffer(match[2], "base64"));
}
function time_it(name, cont) {
var t1 = new Date().getTime();
var ret = cont();
if (ARGS.stats) {
var spent = new Date().getTime() - t1;
if (STATS[name]) STATS[name] += spent;
else STATS[name] = spent;
}
return ret;
}
function print_error(msg) {
console.error("%s", msg);
}
function print(txt) {
console.log("%s", txt);
} }

View File

@@ -182,21 +182,13 @@ var AST_BlockStatement = DEFNODE("BlockStatement", null, {
}, AST_Block); }, AST_Block);
var AST_EmptyStatement = DEFNODE("EmptyStatement", null, { var AST_EmptyStatement = DEFNODE("EmptyStatement", null, {
$documentation: "The empty statement (empty block or simply a semicolon)", $documentation: "The empty statement (empty block or simply a semicolon)"
_walk: function(visitor) {
return visitor._visit(this);
}
}, AST_Statement); }, AST_Statement);
var AST_StatementWithBody = DEFNODE("StatementWithBody", "body", { var AST_StatementWithBody = DEFNODE("StatementWithBody", "body", {
$documentation: "Base class for all statements that contain one nested body: `For`, `ForIn`, `Do`, `While`, `With`", $documentation: "Base class for all statements that contain one nested body: `For`, `ForIn`, `Do`, `While`, `With`",
$propdoc: { $propdoc: {
body: "[AST_Statement] the body; this should always be present, even if it's an AST_EmptyStatement" body: "[AST_Statement] the body; this should always be present, even if it's an AST_EmptyStatement"
},
_walk: function(visitor) {
return visitor._visit(this, function(){
this.body._walk(visitor);
});
} }
}, AST_Statement); }, AST_Statement);
@@ -214,12 +206,13 @@ var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", {
clone: function(deep) { clone: function(deep) {
var node = this._clone(deep); var node = this._clone(deep);
if (deep) { if (deep) {
var refs = node.label.references; var label = node.label;
var label = this.label; var def = this.label;
node.walk(new TreeWalker(function(node) { node.walk(new TreeWalker(function(node) {
if (node instanceof AST_LoopControl if (node instanceof AST_LoopControl
&& node.label && node.label.thedef === label) { && node.label && node.label.thedef === def) {
refs.push(node); node.label.thedef = label;
label.references.push(node);
} }
})); }));
} }
@@ -325,62 +318,13 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", {
$propdoc: { $propdoc: {
globals: "[Object/S] a map of name -> SymbolDef for all undeclared names", globals: "[Object/S] a map of name -> SymbolDef for all undeclared names",
}, },
wrap_enclose: function(arg_parameter_pairs) { wrap_commonjs: function(name) {
var self = this; var body = this.body;
var args = []; var wrapped_tl = "(function(exports){'$ORIG';})(typeof " + name + "=='undefined'?(" + name + "={}):" + name + ");";
var parameters = [];
arg_parameter_pairs.forEach(function(pair) {
var splitAt = pair.lastIndexOf(":");
args.push(pair.substr(0, splitAt));
parameters.push(pair.substr(splitAt + 1));
});
var wrapped_tl = "(function(" + parameters.join(",") + "){ '$ORIG'; })(" + args.join(",") + ")";
wrapped_tl = parse(wrapped_tl); wrapped_tl = parse(wrapped_tl);
wrapped_tl = wrapped_tl.transform(new TreeTransformer(function before(node){ wrapped_tl = wrapped_tl.transform(new TreeTransformer(function before(node){
if (node instanceof AST_Directive && node.value == "$ORIG") { if (node instanceof AST_Directive && node.value == "$ORIG") {
return MAP.splice(self.body); return MAP.splice(body);
}
}));
return wrapped_tl;
},
wrap_commonjs: function(name, export_all) {
var self = this;
var to_export = [];
if (export_all) {
self.figure_out_scope();
self.walk(new TreeWalker(function(node){
if (node instanceof AST_SymbolDeclaration && node.definition().global) {
if (!find_if(function(n){ return n.name == node.name }, to_export))
to_export.push(node);
}
}));
}
var wrapped_tl = "(function(exports, global){ '$ORIG'; '$EXPORTS'; global['" + name + "'] = exports; }({}, (function(){return this}())))";
wrapped_tl = parse(wrapped_tl);
wrapped_tl = wrapped_tl.transform(new TreeTransformer(function before(node){
if (node instanceof AST_Directive) {
switch (node.value) {
case "$ORIG":
return MAP.splice(self.body);
case "$EXPORTS":
var body = [];
to_export.forEach(function(sym){
body.push(new AST_SimpleStatement({
body: new AST_Assign({
left: new AST_Sub({
expression: new AST_SymbolRef({ name: "exports" }),
property: new AST_String({ value: sym.name }),
}),
operator: "=",
right: new AST_SymbolRef(sym),
}),
}));
});
return MAP.splice(body);
}
} }
})); }));
return wrapped_tl; return wrapped_tl;
@@ -600,11 +544,11 @@ var AST_Call = DEFNODE("Call", "expression args", {
}, },
_walk: function(visitor) { _walk: function(visitor) {
return visitor._visit(this, function(){ return visitor._visit(this, function(){
this.expression._walk(visitor);
var args = this.args; var args = this.args;
for (var i = 0, len = args.length; i < len; i++) { for (var i = 0, len = args.length; i < len; i++) {
args[i]._walk(visitor); args[i]._walk(visitor);
} }
this.expression._walk(visitor);
}); });
} }
}); });
@@ -613,68 +557,16 @@ var AST_New = DEFNODE("New", null, {
$documentation: "An object instantiation. Derives from a function call since it has exactly the same properties" $documentation: "An object instantiation. Derives from a function call since it has exactly the same properties"
}, AST_Call); }, AST_Call);
var AST_Seq = DEFNODE("Seq", "car cdr", { var AST_Sequence = DEFNODE("Sequence", "expressions", {
$documentation: "A sequence expression (two comma-separated expressions)", $documentation: "A sequence expression (comma-separated expressions)",
$propdoc: { $propdoc: {
car: "[AST_Node] first element in sequence", expressions: "[AST_Node*] array of expressions (at least two)"
cdr: "[AST_Node] second element in sequence"
},
$cons: function(x, y) {
var seq = new AST_Seq(x);
seq.car = x;
seq.cdr = y;
return seq;
},
$from_array: function(array) {
if (array.length == 0) return null;
if (array.length == 1) return array[0].clone();
var list = null;
for (var i = array.length; --i >= 0;) {
list = AST_Seq.cons(array[i], list);
}
var p = list;
while (p) {
if (p.cdr && !p.cdr.cdr) {
p.cdr = p.cdr.car;
break;
}
p = p.cdr;
}
return list;
},
to_array: function() {
var p = this, a = [];
while (p) {
a.push(p.car);
if (p.cdr && !(p.cdr instanceof AST_Seq)) {
a.push(p.cdr);
break;
}
p = p.cdr;
}
return a;
},
add: function(node) {
var p = this;
while (p) {
if (!(p.cdr instanceof AST_Seq)) {
var cell = AST_Seq.cons(p.cdr, node);
return p.cdr = cell;
}
p = p.cdr;
}
},
len: function() {
if (this.cdr instanceof AST_Seq) {
return this.cdr.len() + 1;
} else {
return 2;
}
}, },
_walk: function(visitor) { _walk: function(visitor) {
return visitor._visit(this, function(){ return visitor._visit(this, function(){
this.car._walk(visitor); this.expressions.forEach(function(node) {
if (this.cdr) this.cdr._walk(visitor); node._walk(visitor);
});
}); });
} }
}); });
@@ -727,7 +619,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",
@@ -981,7 +873,7 @@ TreeWalker.prototype = {
parent: function(n) { parent: function(n) {
return this.stack[this.stack.length - 2 - (n || 0)]; return this.stack[this.stack.length - 2 - (n || 0)];
}, },
push: function (node) { push: function(node) {
if (node instanceof AST_Lambda) { if (node instanceof AST_Lambda) {
this.directives = Object.create(this.directives); this.directives = Object.create(this.directives);
} else if (node instanceof AST_Directive && !this.directives[node.value]) { } else if (node instanceof AST_Directive && !this.directives[node.value]) {
@@ -1035,16 +927,16 @@ TreeWalker.prototype = {
self = p; self = p;
} }
}, },
loopcontrol_target: function(label) { loopcontrol_target: function(node) {
var stack = this.stack; var stack = this.stack;
if (label) for (var i = stack.length; --i >= 0;) { if (node.label) for (var i = stack.length; --i >= 0;) {
var x = stack[i]; var x = stack[i];
if (x instanceof AST_LabeledStatement && x.label.name == label.name) { if (x instanceof AST_LabeledStatement && x.label.name == node.label.name)
return x.body; return x.body;
}
} else for (var i = stack.length; --i >= 0;) { } else for (var i = stack.length; --i >= 0;) {
var x = stack[i]; var x = stack[i];
if (x instanceof AST_Switch || x instanceof AST_IterationStatement) if (x instanceof AST_IterationStatement
|| node instanceof AST_Break && x instanceof AST_Switch)
return x; return x;
} }
} }

File diff suppressed because it is too large Load Diff

154
lib/minify.js Normal file
View File

@@ -0,0 +1,154 @@
"use strict";
var to_ascii = typeof atob == "undefined" ? function(b64) {
return new Buffer(b64, "base64").toString();
} : atob;
var to_base64 = typeof btoa == "undefined" ? function(str) {
return new Buffer(str).toString("base64");
} : btoa;
function read_source_map(code) {
var match = /\n\/\/# sourceMappingURL=data:application\/json(;.*?)?;base64,(.*)/.exec(code);
if (!match) {
AST_Node.warn("inline source map not found");
return null;
}
return to_ascii(match[2]);
}
function set_shorthand(name, options, keys) {
if (options[name]) {
keys.forEach(function(key) {
if (options[key]) {
if (typeof options[key] != "object") options[key] = {};
if (!(name in options[key])) options[key][name] = options[name];
}
});
}
}
function minify(files, options) {
var warn_function = AST_Node.warn_function;
try {
if (typeof files == "string") {
files = [ files ];
}
options = defaults(options, {
compress: {},
ie8: false,
keep_fnames: false,
mangle: {},
output: {},
parse: {},
sourceMap: false,
toplevel: false,
warnings: false,
wrap: false,
}, true);
set_shorthand("ie8", options, [ "compress", "mangle", "output" ]);
set_shorthand("keep_fnames", options, [ "compress", "mangle" ]);
set_shorthand("toplevel", options, [ "compress", "mangle" ]);
set_shorthand("warnings", options, [ "compress" ]);
if (options.mangle) {
options.mangle = defaults(options.mangle, {
cache: null,
eval: false,
ie8: false,
keep_fnames: false,
properties: false,
reserved: [],
toplevel: false,
}, true);
}
if (options.sourceMap) {
options.sourceMap = defaults(options.sourceMap, {
content: null,
filename: null,
includeSources: false,
root: null,
url: null,
}, true);
}
var warnings = [];
if (options.warnings && !AST_Node.warn_function) {
AST_Node.warn_function = function(warning) {
warnings.push(warning);
};
}
var toplevel;
if (files instanceof AST_Toplevel) {
toplevel = files;
} else {
options.parse = options.parse || {};
options.parse.toplevel = null;
for (var name in files) {
options.parse.filename = name;
options.parse.toplevel = parse(files[name], options.parse);
if (options.sourceMap && options.sourceMap.content == "inline") {
if (Object.keys(files).length > 1)
throw new Error("inline source map only works with singular input");
options.sourceMap.content = read_source_map(files[name]);
}
}
toplevel = options.parse.toplevel;
}
if (options.wrap) {
toplevel = toplevel.wrap_commonjs(options.wrap);
}
if (options.compress) {
toplevel.figure_out_scope(options.mangle);
toplevel = new Compressor(options.compress).compress(toplevel);
}
if (options.mangle) {
toplevel.figure_out_scope(options.mangle);
base54.reset();
toplevel.compute_char_frequency(options.mangle);
toplevel.mangle_names(options.mangle);
if (options.mangle.properties) {
toplevel = mangle_properties(toplevel, options.mangle.properties);
}
}
var result = {};
if (options.output.ast) {
result.ast = toplevel;
}
if (!HOP(options.output, "code") || options.output.code) {
if (options.sourceMap) {
if (typeof options.sourceMap.content == "string") {
options.sourceMap.content = JSON.parse(options.sourceMap.content);
}
options.output.source_map = SourceMap({
file: options.sourceMap.filename,
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;
delete options.output.code;
var stream = OutputStream(options.output);
toplevel.print(stream);
result.code = stream.get();
if (options.sourceMap) {
result.map = options.output.source_map.toString();
if (options.sourceMap.url == "inline") {
result.code += "\n//# sourceMappingURL=data:application/json;charset=utf-8;base64," + to_base64(result.map);
} else if (options.sourceMap.url) {
result.code += "\n//# sourceMappingURL=" + options.sourceMap.url;
}
}
}
if (warnings.length) {
result.warnings = warnings;
}
return result;
} catch (ex) {
return { error: ex };
} finally {
AST_Node.warn_function = warn_function;
}
}

View File

@@ -149,7 +149,11 @@
}); });
}, },
SequenceExpression: function(M) { SequenceExpression: function(M) {
return AST_Seq.from_array(M.expressions.map(from_moz)); return new AST_Sequence({
start : my_start_token(M),
end : my_end_token(M),
expressions: M.expressions.map(from_moz)
});
}, },
MemberExpression: function(M) { MemberExpression: function(M) {
return new (M.computed ? AST_Sub : AST_Dot)({ return new (M.computed ? AST_Sub : AST_Dot)({
@@ -332,10 +336,10 @@
}; };
}); });
def_to_moz(AST_Seq, function To_Moz_SequenceExpression(M) { def_to_moz(AST_Sequence, function To_Moz_SequenceExpression(M) {
return { return {
type: "SequenceExpression", type: "SequenceExpression",
expressions: M.to_array().map(to_moz) expressions: M.expressions.map(to_moz)
}; };
}); });

View File

@@ -57,16 +57,16 @@ function OutputStream(options) {
beautify : false, beautify : false,
bracketize : false, bracketize : false,
comments : false, comments : false,
ie8 : false,
indent_level : 4, indent_level : 4,
indent_start : 0, indent_start : 0,
inline_script : false, inline_script : true,
keep_quoted_props: false, keep_quoted_props: false,
max_line_len : false, max_line_len : false,
preamble : null, preamble : null,
preserve_line : false, preserve_line : false,
quote_keys : false, quote_keys : false,
quote_style : 0, quote_style : 0,
screw_ie8 : true,
semicolons : true, semicolons : true,
shebang : true, shebang : true,
source_map : null, source_map : null,
@@ -136,7 +136,7 @@ function OutputStream(options) {
case "\t": return "\\t"; case "\t": return "\\t";
case "\b": return "\\b"; case "\b": return "\\b";
case "\f": return "\\f"; case "\f": return "\\f";
case "\x0B": return options.screw_ie8 ? "\\v" : "\\x0B"; case "\x0B": return options.ie8 ? "\\x0B" : "\\v";
case "\u2028": return "\\u2028"; case "\u2028": return "\\u2028";
case "\u2029": return "\\u2029"; case "\u2029": return "\\u2029";
case "\ufeff": return "\\ufeff"; case "\ufeff": return "\\ufeff";
@@ -190,11 +190,7 @@ function OutputStream(options) {
var might_need_space = false; var might_need_space = false;
var might_need_semicolon = false; var might_need_semicolon = false;
var might_add_newline = 0; var might_add_newline = 0;
var last = null; var last = "";
function last_char() {
return last.charAt(last.length - 1);
};
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) {
@@ -218,10 +214,11 @@ function OutputStream(options) {
function print(str) { function print(str) {
str = String(str); str = String(str);
var ch = str.charAt(0); var ch = str.charAt(0);
var prev = last.charAt(last.length - 1);
if (might_need_semicolon) { if (might_need_semicolon) {
might_need_semicolon = false; might_need_semicolon = false;
if ((!ch || ";}".indexOf(ch) < 0) && !/[;]$/.test(last)) { if (prev == ":" && ch == "}" || (!ch || ";}".indexOf(ch) < 0) && prev != ";") {
if (options.semicolons || requireSemicolonChars(ch)) { if (options.semicolons || requireSemicolonChars(ch)) {
OUTPUT += ";"; OUTPUT += ";";
current_col++; current_col++;
@@ -258,7 +255,6 @@ function OutputStream(options) {
} }
if (might_need_space) { if (might_need_space) {
var prev = last_char();
if ((is_identifier_char(prev) if ((is_identifier_char(prev)
&& (is_identifier_char(ch) || ch == "\\")) && (is_identifier_char(ch) || ch == "\\"))
|| (ch == "/" && ch == prev) || (ch == "/" && ch == prev)
@@ -592,7 +588,7 @@ function OutputStream(options) {
|| p instanceof AST_Call && p.expression === this; || p instanceof AST_Call && p.expression === this;
}); });
PARENS(AST_Seq, function(output){ PARENS(AST_Sequence, function(output){
var p = output.parent(); var p = output.parent();
return p instanceof AST_Call // (foo, bar)() or foo(1, (2, 3), 4) return p instanceof AST_Call // (foo, bar)() or foo(1, (2, 3), 4)
|| p instanceof AST_Unary // !(foo, bar, baz) || p instanceof AST_Unary // !(foo, bar, baz)
@@ -681,7 +677,7 @@ function OutputStream(options) {
} }
}); });
PARENS([ AST_Assign, AST_Conditional ], function (output){ PARENS([ AST_Assign, AST_Conditional ], function(output){
var p = output.parent(); var p = output.parent();
// !(a = false) → true // !(a = false) → true
if (p instanceof AST_Unary) if (p instanceof AST_Unary)
@@ -906,7 +902,7 @@ function OutputStream(options) {
function make_then(self, output) { function make_then(self, output) {
var b = self.body; var b = self.body;
if (output.option("bracketize") if (output.option("bracketize")
|| !output.option("screw_ie8") && b instanceof AST_Do) || output.option("ie8") && b instanceof AST_Do)
return make_block(b, output); return make_block(b, output);
// The squeezer replaces "block"-s that contain only a single // The squeezer replaces "block"-s that contain only a single
// statement with the statement itself; technically, the AST // statement with the statement itself; technically, the AST
@@ -1087,18 +1083,19 @@ function OutputStream(options) {
AST_Call.prototype._codegen(self, output); AST_Call.prototype._codegen(self, output);
}); });
AST_Seq.DEFMETHOD("_do_print", function(output){ AST_Sequence.DEFMETHOD("_do_print", function(output){
this.car.print(output); this.expressions.forEach(function(node, index) {
if (this.cdr) { if (index > 0) {
output.comma(); output.comma();
if (output.should_break()) { if (output.should_break()) {
output.newline(); output.newline();
output.indent(); output.indent();
}
} }
this.cdr.print(output); node.print(output);
} });
}); });
DEFPRINT(AST_Seq, function(self, output){ DEFPRINT(AST_Sequence, function(self, output){
self._do_print(output); self._do_print(output);
// var p = output.parent(); // var p = output.parent();
// if (p instanceof AST_Statement) { // if (p instanceof AST_Statement) {
@@ -1221,7 +1218,7 @@ function OutputStream(options) {
&& +key + "" == key) && +key + "" == key)
&& parseFloat(key) >= 0) { && parseFloat(key) >= 0) {
output.print(make_num(key)); output.print(make_num(key));
} else if (RESERVED_WORDS(key) ? output.option("screw_ie8") : is_identifier_string(key)) { } else if (RESERVED_WORDS(key) ? !output.option("ie8") : is_identifier_string(key)) {
if (quote && output.option("keep_quoted_props")) { if (quote && output.option("keep_quoted_props")) {
output.print_string(key, quote); output.print_string(key, quote);
} else { } else {

View File

@@ -111,7 +111,7 @@ var WHITESPACE_CHARS = makePredicate(characters(" \u00a0\n\r\t\f\u000b\u200b\u20
var NEWLINE_CHARS = makePredicate(characters("\n\r\u2028\u2029")); var NEWLINE_CHARS = makePredicate(characters("\n\r\u2028\u2029"));
var PUNC_BEFORE_EXPRESSION = makePredicate(characters("[{(,.;:")); var PUNC_BEFORE_EXPRESSION = makePredicate(characters("[{(,;:"));
var PUNC_CHARS = makePredicate(characters("[]{}(),;:")); var PUNC_CHARS = makePredicate(characters("[]{}(),;:"));
@@ -629,8 +629,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
} }
next_token.has_directive = function(directive) { next_token.has_directive = function(directive) {
return S.directives[directive] !== undefined && return S.directives[directive] > 0;
S.directives[directive] > 0;
} }
return next_token; return next_token;
@@ -689,7 +688,6 @@ function parse($TEXT, options) {
options = defaults(options, { options = defaults(options, {
bare_returns : false, bare_returns : false,
cli : false,
expression : false, expression : false,
filename : null, filename : null,
html5_comments : true, html5_comments : true,
@@ -1034,29 +1032,32 @@ function parse($TEXT, options) {
if (in_statement && !name) if (in_statement && !name)
unexpected(); unexpected();
expect("("); expect("(");
var argnames = [];
for (var first = true; !is("punc", ")");) {
if (first) first = false; else expect(",");
argnames.push(as_symbol(AST_SymbolFunarg));
}
next();
var loop = S.in_loop;
var labels = S.labels;
++S.in_function;
S.in_directives = true;
S.input.push_directives_stack();
S.in_loop = 0;
S.labels = [];
var body = block_();
if (S.input.has_directive("use strict")) {
if (name) strict_verify_symbol(name);
argnames.forEach(strict_verify_symbol);
}
S.input.pop_directives_stack();
--S.in_function;
S.in_loop = loop;
S.labels = labels;
return new ctor({ return new ctor({
name: name, name: name,
argnames: (function(first, a){ argnames: argnames,
while (!is("punc", ")")) { body: body
if (first) first = false; else expect(",");
a.push(as_symbol(AST_SymbolFunarg));
}
next();
return a;
})(true, []),
body: (function(loop, labels){
++S.in_function;
S.in_directives = true;
S.input.push_directives_stack();
S.in_loop = 0;
S.labels = [];
var a = block_();
S.input.pop_directives_stack();
--S.in_function;
S.in_loop = loop;
S.labels = labels;
return a;
})(S.in_loop, S.labels)
}); });
}; };
@@ -1158,7 +1159,10 @@ function parse($TEXT, options) {
a.push(new AST_VarDef({ a.push(new AST_VarDef({
start : S.token, start : S.token,
name : as_symbol(in_const ? AST_SymbolConst : AST_SymbolVar), name : as_symbol(in_const ? AST_SymbolConst : AST_SymbolVar),
value : is("operator", "=") ? (next(), expression(false, no_in)) : null, value : is("operator", "=")
? (next(), expression(false, no_in))
: in_const && S.input.has_directive("use strict")
? croak("Missing initializer in const declaration") : null,
end : prev() end : prev()
})); }));
if (!is("punc", ",")) if (!is("punc", ","))
@@ -1354,14 +1358,15 @@ function parse($TEXT, options) {
function as_property_name() { function as_property_name() {
var tmp = S.token; var tmp = S.token;
next();
switch (tmp.type) { switch (tmp.type) {
case "operator":
if (!KEYWORDS(tmp.value)) unexpected();
case "num": case "num":
case "string": case "string":
case "name": case "name":
case "operator":
case "keyword": case "keyword":
case "atom": case "atom":
next();
return tmp.value; return tmp.value;
default: default:
unexpected(); unexpected();
@@ -1370,16 +1375,9 @@ function parse($TEXT, options) {
function as_name() { function as_name() {
var tmp = S.token; var tmp = S.token;
if (tmp.type != "name") unexpected();
next(); next();
switch (tmp.type) { return tmp.value;
case "name":
case "operator":
case "keyword":
case "atom":
return tmp.value;
default:
unexpected();
}
}; };
function _make_symbol(type) { function _make_symbol(type) {
@@ -1391,12 +1389,20 @@ function parse($TEXT, options) {
}); });
}; };
function strict_verify_symbol(sym) {
if (sym.name == "arguments" || sym.name == "eval")
croak("Unexpected " + sym.name + " in strict mode", sym.start.line, sym.start.col, sym.start.pos);
}
function as_symbol(type, noerror) { function as_symbol(type, noerror) {
if (!is("name")) { if (!is("name")) {
if (!noerror) croak("Name expected"); if (!noerror) croak("Name expected");
return null; return null;
} }
var sym = _make_symbol(type); var sym = _make_symbol(type);
if (S.input.has_directive("use strict") && sym instanceof AST_SymbolDeclaration) {
strict_verify_symbol(sym);
}
next(); next();
return sym; return sym;
}; };
@@ -1440,14 +1446,14 @@ function parse($TEXT, options) {
if (is("operator") && UNARY_PREFIX(start.value)) { if (is("operator") && UNARY_PREFIX(start.value)) {
next(); next();
handle_regexp(); handle_regexp();
var ex = make_unary(AST_UnaryPrefix, start.value, maybe_unary(allow_calls)); var ex = make_unary(AST_UnaryPrefix, start, maybe_unary(allow_calls));
ex.start = start; ex.start = start;
ex.end = prev(); ex.end = prev();
return ex; return ex;
} }
var val = expr_atom(allow_calls); var val = expr_atom(allow_calls);
while (is("operator") && UNARY_POSTFIX(S.token.value) && !S.token.nlb) { while (is("operator") && UNARY_POSTFIX(S.token.value) && !S.token.nlb) {
val = make_unary(AST_UnaryPostfix, S.token.value, val); val = make_unary(AST_UnaryPostfix, S.token, val);
val.start = start; val.start = start;
val.end = S.token; val.end = S.token;
next(); next();
@@ -1455,9 +1461,19 @@ function parse($TEXT, options) {
return val; return val;
}; };
function make_unary(ctor, op, expr) { function make_unary(ctor, token, expr) {
if ((op == "++" || op == "--") && !is_assignable(expr)) var op = token.value;
croak("Invalid use of " + op + " operator", null, ctor === AST_UnaryPrefix ? expr.start.col - 1 : null); switch (op) {
case "++":
case "--":
if (!is_assignable(expr))
croak("Invalid use of " + op + " operator", token.line, token.col, token.pos);
break;
case "delete":
if (expr instanceof AST_SymbolRef && S.input.has_directive("use strict"))
croak("Calling delete on expression not allowed in strict mode", expr.start.line, expr.start.col, expr.start.pos);
break;
}
return new ctor({ operator: op, expression: expr }); return new ctor({ operator: op, expression: expr });
}; };
@@ -1502,7 +1518,6 @@ function parse($TEXT, options) {
}; };
function is_assignable(expr) { function is_assignable(expr) {
if (options.cli) return true;
return expr instanceof AST_PropAccess || expr instanceof AST_SymbolRef; return expr instanceof AST_PropAccess || expr instanceof AST_SymbolRef;
}; };
@@ -1527,17 +1542,18 @@ function parse($TEXT, options) {
var expression = function(commas, no_in) { var expression = function(commas, no_in) {
var start = S.token; var start = S.token;
var expr = maybe_assign(no_in); var exprs = [];
if (commas && is("punc", ",")) { while (true) {
exprs.push(maybe_assign(no_in));
if (!commas || !is("punc", ",")) break;
next(); next();
return new AST_Seq({ commas = true;
start : start,
car : expr,
cdr : expression(true, no_in),
end : peek()
});
} }
return expr; return exprs.length == 1 ? exprs[0] : new AST_Sequence({
start : start,
expressions : exprs,
end : peek()
});
}; };
function in_loop(cont) { function in_loop(cont) {

View File

@@ -43,8 +43,16 @@
"use strict"; "use strict";
function find_builtins() { function find_builtins(reserved) {
var a = []; // NaN will be included due to Number.NaN
[
"null",
"true",
"false",
"Infinity",
"-Infinity",
"undefined",
].forEach(add);
[ Object, Array, Function, Number, [ Object, Array, Function, Number,
String, Boolean, Error, Math, String, Boolean, Error, Math,
Date, RegExp Date, RegExp
@@ -55,24 +63,23 @@ function find_builtins() {
} }
}); });
function add(name) { function add(name) {
push_uniq(a, name); push_uniq(reserved, name);
} }
return a;
} }
function mangle_properties(ast, options) { function mangle_properties(ast, options) {
options = defaults(options, { options = defaults(options, {
builtins: false,
cache: null, cache: null,
debug: false, debug: false,
ignore_quoted: false, keep_quoted: false,
only_cache: false, only_cache: false,
regex: null, regex: null,
reserved: null, reserved: null,
}); });
var reserved = options.reserved; var reserved = options.reserved || [];
if (reserved == null) if (!options.builtins) find_builtins(reserved);
reserved = find_builtins();
var cache = options.cache; var cache = options.cache;
if (cache == null) { if (cache == null) {
@@ -83,12 +90,12 @@ function mangle_properties(ast, options) {
} }
var regex = options.regex; var regex = options.regex;
var ignore_quoted = options.ignore_quoted; var keep_quoted = options.keep_quoted;
// note debug is either false (disabled), or a string of the debug suffix to use (enabled). // note debug is either false (disabled), or a string of the debug suffix to use (enabled).
// note debug may be enabled as an empty string, which is falsey. Also treat passing 'true' // note debug may be enabled as an empty string, which is falsey. Also treat passing 'true'
// the same as passing an empty string. // the same as passing an empty string.
var debug = (options.debug !== false); var debug = options.debug !== false;
var debug_name_suffix; var debug_name_suffix;
if (debug) { if (debug) {
debug_name_suffix = (options.debug === true ? "" : options.debug); debug_name_suffix = (options.debug === true ? "" : options.debug);
@@ -96,12 +103,12 @@ function mangle_properties(ast, options) {
var names_to_mangle = []; var names_to_mangle = [];
var unmangleable = []; var unmangleable = [];
var ignored = {}; var to_keep = {};
// step 1: find candidates to mangle // step 1: find candidates to mangle
ast.walk(new TreeWalker(function(node){ ast.walk(new TreeWalker(function(node){
if (node instanceof AST_ObjectKeyVal) { if (node instanceof AST_ObjectKeyVal) {
add(node.key, ignore_quoted && node.quote); add(node.key, keep_quoted && node.quote);
} }
else if (node instanceof AST_ObjectProperty) { else if (node instanceof AST_ObjectProperty) {
// setter or getter, since KeyVal is handled above // setter or getter, since KeyVal is handled above
@@ -111,14 +118,14 @@ function mangle_properties(ast, options) {
add(node.property); add(node.property);
} }
else if (node instanceof AST_Sub) { else if (node instanceof AST_Sub) {
addStrings(node.property, ignore_quoted); addStrings(node.property, keep_quoted);
} }
})); }));
// step 2: transform the tree, renaming properties // step 2: transform the tree, renaming properties
return ast.transform(new TreeTransformer(function(node){ return ast.transform(new TreeTransformer(function(node){
if (node instanceof AST_ObjectKeyVal) { if (node instanceof AST_ObjectKeyVal) {
if (!(ignore_quoted && node.quote)) if (!(keep_quoted && node.quote))
node.key = mangle(node.key); node.key = mangle(node.key);
} }
else if (node instanceof AST_ObjectProperty) { else if (node instanceof AST_ObjectProperty) {
@@ -129,7 +136,7 @@ function mangle_properties(ast, options) {
node.property = mangle(node.property); node.property = mangle(node.property);
} }
else if (node instanceof AST_Sub) { else if (node instanceof AST_Sub) {
if (!ignore_quoted) if (!keep_quoted)
node.property = mangleStrings(node.property); node.property = mangleStrings(node.property);
} }
// else if (node instanceof AST_String) { // else if (node instanceof AST_String) {
@@ -149,27 +156,26 @@ function mangle_properties(ast, options) {
// only function declarations after this line // only function declarations after this line
function can_mangle(name) { function can_mangle(name) {
if (!is_identifier(name)) return false;
if (unmangleable.indexOf(name) >= 0) return false; if (unmangleable.indexOf(name) >= 0) return false;
if (reserved.indexOf(name) >= 0) return false; if (reserved.indexOf(name) >= 0) return false;
if (options.only_cache) { if (options.only_cache) {
return cache.props.has(name); return cache.props.has(name);
} }
if (/^[0-9.]+$/.test(name)) return false; if (/^-?[0-9]+(\.[0-9]+)?(e[+-][0-9]+)?$/.test(name)) return false;
return true; return true;
} }
function should_mangle(name) { function should_mangle(name) {
if (ignore_quoted && name in ignored) return false; if (keep_quoted && name in to_keep) return false;
if (regex && !regex.test(name)) return false; if (regex && !regex.test(name)) return false;
if (reserved.indexOf(name) >= 0) return false; if (reserved.indexOf(name) >= 0) return false;
return cache.props.has(name) return cache.props.has(name)
|| names_to_mangle.indexOf(name) >= 0; || names_to_mangle.indexOf(name) >= 0;
} }
function add(name, ignore) { function add(name, keep) {
if (ignore) { if (keep) {
ignored[name] = true; to_keep[name] = true;
return; return;
} }
@@ -192,19 +198,19 @@ function mangle_properties(ast, options) {
// debug mode: use a prefix and suffix to preserve readability, e.g. o.foo -> o._$foo$NNN_. // debug mode: use a prefix and suffix to preserve readability, e.g. o.foo -> o._$foo$NNN_.
var debug_mangled = "_$" + name + "$" + debug_name_suffix + "_"; var debug_mangled = "_$" + name + "$" + debug_name_suffix + "_";
if (can_mangle(debug_mangled) && !(ignore_quoted && debug_mangled in ignored)) { if (can_mangle(debug_mangled) && !(keep_quoted && debug_mangled in to_keep)) {
mangled = debug_mangled; mangled = debug_mangled;
} }
} }
// either debug mode is off, or it is on and we could not use the mangled name // either debug mode is off, or it is on and we could not use the mangled name
if (!mangled) { if (!mangled) {
// note can_mangle() does not check if the name collides with the 'ignored' set // Note: `can_mangle()` does not check if the name collides with the `to_keep` set
// (filled with quoted properties when ignore_quoted set). Make sure we add this // (filled with quoted properties when `keep_quoted` is set). Make sure we add this
// check so we don't collide with a quoted name. // check so we don't collide with a quoted name.
do { do {
mangled = base54(++cache.cname); mangled = base54(++cache.cname);
} while (!can_mangle(mangled) || (ignore_quoted && mangled in ignored)); } while (!can_mangle(mangled) || keep_quoted && mangled in to_keep);
} }
cache.props.set(name, mangled); cache.props.set(name, mangled);
@@ -212,17 +218,17 @@ function mangle_properties(ast, options) {
return mangled; return mangled;
} }
function addStrings(node, ignore) { function addStrings(node, keep) {
var out = {}; var out = {};
try { try {
(function walk(node){ (function walk(node){
node.walk(new TreeWalker(function(node){ node.walk(new TreeWalker(function(node){
if (node instanceof AST_Seq) { if (node instanceof AST_Sequence) {
walk(node.cdr); walk(node.expressions[node.expressions.length - 1]);
return true; return true;
} }
if (node instanceof AST_String) { if (node instanceof AST_String) {
add(node.value, ignore); add(node.value, keep);
return true; return true;
} }
if (node instanceof AST_Conditional) { if (node instanceof AST_Conditional) {
@@ -240,8 +246,9 @@ function mangle_properties(ast, options) {
function mangleStrings(node) { function mangleStrings(node) {
return node.transform(new TreeTransformer(function(node){ return node.transform(new TreeTransformer(function(node){
if (node instanceof AST_Seq) { if (node instanceof AST_Sequence) {
node.cdr = mangleStrings(node.cdr); var last = node.expressions.length - 1;
node.expressions[last] = mangleStrings(node.expressions[last]);
} }
else if (node instanceof AST_String) { else if (node instanceof AST_String) {
node.value = mangle(node.value); node.value = mangle(node.value);
@@ -253,5 +260,4 @@ function mangle_properties(ast, options) {
return node; return node;
})); }));
} }
} }

View File

@@ -76,7 +76,7 @@ SymbolDef.prototype = {
else if (!this.mangled_name && !this.unmangleable(options)) { else if (!this.mangled_name && !this.unmangleable(options)) {
var s = this.scope; var s = this.scope;
var sym = this.orig[0]; var sym = this.orig[0];
if (!options.screw_ie8 && sym instanceof AST_SymbolLambda) if (options.ie8 && sym instanceof AST_SymbolLambda)
s = s.parent_scope; s = s.parent_scope;
var def; var def;
if (this.defun && (def = this.defun.variables.get(this.name))) { if (this.defun && (def = this.defun.variables.get(this.name))) {
@@ -93,7 +93,7 @@ SymbolDef.prototype = {
AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
options = defaults(options, { options = defaults(options, {
cache: null, cache: null,
screw_ie8: true, ie8: false,
}); });
// pass 1: setup scope chaining and handle definitions // pass 1: setup scope chaining and handle definitions
@@ -220,7 +220,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
self.walk(tw); self.walk(tw);
// pass 3: fix up any scoping issue with IE8 // pass 3: fix up any scoping issue with IE8
if (!options.screw_ie8) { if (options.ie8) {
self.walk(new TreeWalker(function(node, descend) { self.walk(new TreeWalker(function(node, descend) {
if (node instanceof AST_SymbolCatch) { if (node instanceof AST_SymbolCatch) {
var name = node.name; var name = node.name;
@@ -268,7 +268,7 @@ AST_Scope.DEFMETHOD("init_scope_vars", function(parent_scope){
AST_Lambda.DEFMETHOD("init_scope_vars", function(){ AST_Lambda.DEFMETHOD("init_scope_vars", function(){
AST_Scope.prototype.init_scope_vars.apply(this, arguments); AST_Scope.prototype.init_scope_vars.apply(this, arguments);
this.uses_arguments = false; this.uses_arguments = false;
this.def_variable(new AST_SymbolVar({ this.def_variable(new AST_SymbolConst({
name: "arguments", name: "arguments",
start: this.start, start: this.start,
end: this.end end: this.end
@@ -325,8 +325,8 @@ AST_Scope.DEFMETHOD("next_mangled", function(options){
if (!is_identifier(m)) continue; // skip over "do" if (!is_identifier(m)) continue; // skip over "do"
// https://github.com/mishoo/UglifyJS2/issues/242 -- do not // https://github.com/mishoo/UglifyJS2/issues/242 -- do not
// shadow a name excepted from mangling. // shadow a name reserved from mangling.
if (options.except.indexOf(m) >= 0) continue; if (options.reserved.indexOf(m) >= 0) continue;
// we must ensure that the mangled name does not shadow a name // we must ensure that the mangled name does not shadow a name
// from some parent scope that is referenced in this or in // from some parent scope that is referenced in this or in
@@ -399,10 +399,9 @@ AST_Symbol.DEFMETHOD("global", function(){
AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){ AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){
return defaults(options, { return defaults(options, {
eval : false, eval : false,
except : [], ie8 : false,
keep_fnames : false, keep_fnames : false,
screw_ie8 : true, reserved : [],
sort : false, // Ignored. Flag retained for backwards compatibility.
toplevel : false, toplevel : false,
}); });
}); });
@@ -411,7 +410,7 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
options = this._default_mangler_options(options); options = this._default_mangler_options(options);
// Never mangle arguments // Never mangle arguments
options.except.push('arguments'); options.reserved.push('arguments');
// We only need to mangle declaration nodes. Special logic wired // We only need to mangle declaration nodes. Special logic wired
// into the code generator will display the mangled name if it's // into the code generator will display the mangled name if it's
@@ -422,7 +421,7 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
if (options.cache) { if (options.cache) {
this.globals.each(function(symbol){ this.globals.each(function(symbol){
if (options.except.indexOf(symbol.name) < 0) { if (options.reserved.indexOf(symbol.name) < 0) {
to_mangle.push(symbol); to_mangle.push(symbol);
} }
}); });
@@ -439,7 +438,7 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
if (node instanceof AST_Scope) { if (node instanceof AST_Scope) {
var p = tw.parent(), a = []; var p = tw.parent(), a = [];
node.variables.each(function(symbol){ node.variables.each(function(symbol){
if (options.except.indexOf(symbol.name) < 0) { if (options.reserved.indexOf(symbol.name) < 0) {
a.push(symbol); a.push(symbol);
} }
}); });
@@ -452,7 +451,7 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
node.mangled_name = name; node.mangled_name = name;
return true; return true;
} }
if (options.screw_ie8 && node instanceof AST_SymbolCatch) { if (!options.ie8 && node instanceof AST_SymbolCatch) {
to_mangle.push(node.definition()); to_mangle.push(node.definition());
return; return;
} }
@@ -573,89 +572,3 @@ var base54 = (function() {
}; };
return base54; return base54;
})(); })();
AST_Toplevel.DEFMETHOD("scope_warnings", function(options){
options = defaults(options, {
assign_to_global : true,
eval : true,
func_arguments : true,
nested_defuns : true,
undeclared : false, // this makes a lot of noise
unreferenced : true,
});
var tw = new TreeWalker(function(node){
if (options.undeclared
&& node instanceof AST_SymbolRef
&& node.undeclared())
{
// XXX: this also warns about JS standard names,
// i.e. Object, Array, parseInt etc. Should add a list of
// exceptions.
AST_Node.warn("Undeclared symbol: {name} [{file}:{line},{col}]", {
name: node.name,
file: node.start.file,
line: node.start.line,
col: node.start.col
});
}
if (options.assign_to_global)
{
var sym = null;
if (node instanceof AST_Assign && node.left instanceof AST_SymbolRef)
sym = node.left;
else if (node instanceof AST_ForIn && node.init instanceof AST_SymbolRef)
sym = node.init;
if (sym
&& (sym.undeclared()
|| (sym.global() && sym.scope !== sym.definition().scope))) {
AST_Node.warn("{msg}: {name} [{file}:{line},{col}]", {
msg: sym.undeclared() ? "Accidental global?" : "Assignment to global",
name: sym.name,
file: sym.start.file,
line: sym.start.line,
col: sym.start.col
});
}
}
if (options.eval
&& node instanceof AST_SymbolRef
&& node.undeclared()
&& node.name == "eval") {
AST_Node.warn("Eval is used [{file}:{line},{col}]", node.start);
}
if (options.unreferenced
&& (node instanceof AST_SymbolDeclaration || node instanceof AST_Label)
&& !(node instanceof AST_SymbolCatch)
&& node.unreferenced()) {
AST_Node.warn("{type} {name} is declared but not referenced [{file}:{line},{col}]", {
type: node instanceof AST_Label ? "Label" : "Symbol",
name: node.name,
file: node.start.file,
line: node.start.line,
col: node.start.col
});
}
if (options.func_arguments
&& node instanceof AST_Lambda
&& node.uses_arguments) {
AST_Node.warn("arguments used in function {name} [{file}:{line},{col}]", {
name: node.name ? node.name.name : "anonymous",
file: node.start.file,
line: node.start.line,
col: node.start.col
});
}
if (options.nested_defuns
&& node instanceof AST_Defun
&& !(tw.parent() instanceof AST_Scope)) {
AST_Node.warn("Function {name} declared in nested statement \"{type}\" [{file}:{line},{col}]", {
name: node.name.name,
type: tw.parent().TYPE,
file: node.start.file,
line: node.start.line,
col: node.start.col
});
}
});
this.walk(tw);
});

View File

@@ -174,9 +174,8 @@ TreeTransformer.prototype = new TreeWalker;
self.args = do_list(self.args, tw); self.args = do_list(self.args, tw);
}); });
_(AST_Seq, function(self, tw){ _(AST_Sequence, function(self, tw){
self.car = self.car.transform(tw); self.expressions = do_list(self.expressions, tw);
self.cdr = self.cdr.transform(tw);
}); });
_(AST_Dot, function(self, tw){ _(AST_Dot, function(self, tw){

View File

@@ -346,7 +346,7 @@ function first_in_statement(stack) {
for (var i = 0, p; p = stack.parent(i); i++) { for (var i = 0, p; p = stack.parent(i); i++) {
if (p instanceof AST_Statement && p.body === node) if (p instanceof AST_Statement && p.body === node)
return true; return true;
if ((p instanceof AST_Seq && p.car === node ) || if ((p instanceof AST_Sequence && p.expressions[0] === node) ||
(p instanceof AST_Call && p.expression === node && !(p instanceof AST_New) ) || (p instanceof AST_Call && p.expression === node && !(p instanceof AST_New) ) ||
(p instanceof AST_Dot && p.expression === node ) || (p instanceof AST_Dot && p.expression === node ) ||
(p instanceof AST_Sub && p.expression === node ) || (p instanceof AST_Sub && p.expression === node ) ||

View File

@@ -4,7 +4,7 @@
"homepage": "http://lisperator.net/uglifyjs", "homepage": "http://lisperator.net/uglifyjs",
"author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)", "author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"version": "2.8.21", "version": "3.0.1",
"engines": { "engines": {
"node": ">=0.8.0" "node": ">=0.8.0"
}, },
@@ -29,8 +29,8 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"source-map": "~0.5.1", "commander": "~2.9.0",
"yargs": "~3.10.0" "source-map": "~0.5.1"
}, },
"devDependencies": { "devDependencies": {
"acorn": "~0.6.0", "acorn": "~0.6.0",

View File

@@ -7,12 +7,12 @@ var createHash = require("crypto").createHash;
var fork = require("child_process").fork; var fork = require("child_process").fork;
var args = process.argv.slice(2); var args = process.argv.slice(2);
if (!args.length) { if (!args.length) {
args.push("-mc", "warnings=false"); args.push("-mc");
} }
args.push("--stats"); args.push("--stats");
var urls = [ var urls = [
"https://code.jquery.com/jquery-3.1.1.js", "https://code.jquery.com/jquery-3.2.1.js",
"https://code.angularjs.org/1.6.1/angular.js", "https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.4/angular.js",
"https://cdnjs.cloudflare.com/ajax/libs/mathjs/3.9.0/math.js", "https://cdnjs.cloudflare.com/ajax/libs/mathjs/3.9.0/math.js",
"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.js", "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.js",
"https://unpkg.com/react@15.3.2/dist/react.js", "https://unpkg.com/react@15.3.2/dist/react.js",
@@ -29,11 +29,11 @@ function done() {
var info = results[url]; var info = results[url];
console.log(); console.log();
console.log(url); console.log(url);
console.log(info.log);
var elapsed = 0; var elapsed = 0;
info.log.replace(/: ([0-9]+\.[0-9]{3})s/g, function(match, time) { console.log(info.log.replace(/Elapsed: ([0-9]+)\s*/g, function(match, time) {
elapsed += parseFloat(time); elapsed += 1e-3 * parseInt(time);
}); return "";
}));
console.log("Run-time:", elapsed.toFixed(3), "s"); console.log("Run-time:", elapsed.toFixed(3), "s");
console.log("Original:", info.input, "bytes"); console.log("Original:", info.input, "bytes");
console.log("Uglified:", info.output, "bytes"); console.log("Uglified:", info.output, "bytes");

View File

@@ -1,67 +0,0 @@
ng_inject_defun: {
options = {
angular: true
};
input: {
/*@ngInject*/
function Controller(dependency) {
return dependency;
}
}
expect: {
function Controller(dependency) {
return dependency;
}
Controller.$inject=['dependency']
}
}
ng_inject_assignment: {
options = {
angular: true
};
input: {
/*@ngInject*/
var Controller = function(dependency) {
return dependency;
}
}
expect: {
var Controller = function(dependency) {
return dependency;
}
Controller.$inject=['dependency']
}
}
ng_inject_inline: {
options = {
angular: true
};
input: {
angular.module('a').
factory('b',
/*@ngInject*/
function(dependency) {
return dependency;
}).
directive('c',
/*@ngInject*/
function(anotherDependency) {
return anotherDependency;
})
}
expect: {
angular.module('a').
factory('b',[
'dependency',
function(dependency) {
return dependency;
}]).
directive('c',[
'anotherDependency',
function(anotherDependency) {
return anotherDependency;
}])
}
}

View File

@@ -2,7 +2,7 @@ ascii_only_true: {
options = {} options = {}
beautify = { beautify = {
ascii_only : true, ascii_only : true,
screw_ie8 : true, ie8 : false,
beautify : false, beautify : false,
} }
input: { input: {
@@ -20,7 +20,7 @@ ascii_only_false: {
options = {} options = {}
beautify = { beautify = {
ascii_only : false, ascii_only : false,
screw_ie8 : true, ie8 : false,
beautify : false, beautify : false,
} }
input: { input: {
@@ -33,4 +33,3 @@ ascii_only_false: {
} }
expect_exact: 'function f(){return"\\x000\\x001\\x007\\08\\0"+"\\0\x01\x02\x03\x04\x05\x06\x07\\b\\t\\n\\v\\f\\r\x0e\x0f"+"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"+\' !"# ... }~\x7f\x80\x81 ... \xfe\xff\u0fff\uffff\'}' expect_exact: 'function f(){return"\\x000\\x001\\x007\\08\\0"+"\\0\x01\x02\x03\x04\x05\x06\x07\\b\\t\\n\\v\\f\\r\x0e\x0f"+"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"+\' !"# ... }~\x7f\x80\x81 ... \xfe\xff\u0fff\uffff\'}'
} }

View File

@@ -2,7 +2,7 @@ collapse_vars_side_effects_1: {
options = { options = {
collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true,
comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true,
keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true, reduce_vars:true
} }
input: { input: {
function f1() { function f1() {
@@ -68,11 +68,10 @@ collapse_vars_side_effects_1: {
log(x, s.charAt(i++), y, 7); log(x, s.charAt(i++), y, 7);
} }
function f4() { function f4() {
var log = console.log.bind(console), var i = 10,
i = 10,
x = i += 2, x = i += 2,
y = i += 3; y = i += 3;
log(x, i += 4, y, i); console.log.bind(console)(x, i += 4, y, i);
} }
f1(), f2(), f3(), f4(); f1(), f2(), f3(), f4();
} }
@@ -151,7 +150,7 @@ collapse_vars_issue_721: {
options = { options = {
collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true,
comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true,
keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true, reduce_vars:true
} }
input: { input: {
define(["require", "exports", 'handlebars'], function (require, exports, hb) { define(["require", "exports", 'handlebars'], function (require, exports, hb) {
@@ -217,7 +216,7 @@ collapse_vars_properties: {
options = { options = {
collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true,
comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true,
keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true, reduce_vars:true
} }
input: { input: {
function f1(obj) { function f1(obj) {
@@ -244,7 +243,7 @@ collapse_vars_if: {
options = { options = {
collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true,
comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true,
keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true, reduce_vars:true
} }
input: { input: {
function f1() { function f1() {
@@ -294,7 +293,7 @@ collapse_vars_while: {
options = { options = {
collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true,
comparisons:true, evaluate:true, booleans:true, loops:false, unused:true, hoist_funs:true, comparisons:true, evaluate:true, booleans:true, loops:false, unused:true, hoist_funs:true,
keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true, reduce_vars:true
} }
input: { input: {
function f1(y) { function f1(y) {
@@ -393,9 +392,9 @@ collapse_vars_do_while: {
} }
function f3(y) { function f3(y) {
function fn(n) { console.log(n); } function fn(n) { console.log(n); }
var a = 2; var a = 2, x = 7;
do { do {
fn(a = 7); fn(a = x);
break; break;
} while (y); } while (y);
} }
@@ -468,8 +467,9 @@ collapse_vars_do_while_drop_assign: {
} }
function f3(y) { function f3(y) {
function fn(n) { console.log(n); } function fn(n) { console.log(n); }
var x = 7;
do { do {
fn(7); fn(x);
break; break;
} while (y); } while (y);
} }
@@ -670,8 +670,8 @@ collapse_vars_lvalues: {
function f4(x) { var a = (x -= 3); return x + a; } function f4(x) { var a = (x -= 3); return x + a; }
function f5(x) { var w = e1(), v = e2(), c = v = --x; return (w = x) - c; } function f5(x) { var w = e1(), v = e2(), c = v = --x; return (w = x) - c; }
function f6(x) { var w = e1(), v = e2(); return (v = --x) - (w = x); } function f6(x) { var w = e1(), v = e2(); return (v = --x) - (w = x); }
function f7(x) { var w = e1(), v = e2(), c = v - x; return (w = x) - c; } function f7(x) { var w = e1(); return (w = x) - (e2() - x); }
function f8(x) { var w = e1(), v = e2(); return (w = x) - (v - x); } function f8(x) { var w = e1(); return (w = x) - (e2() - x); }
function f9(x) { var w = e1(); return e2() - x - (w = x); } function f9(x) { var w = e1(); return e2() - x - (w = x); }
} }
} }
@@ -700,10 +700,10 @@ collapse_vars_lvalues_drop_assign: {
function f2(x) { var z = x, a = ++z; return z += a; } function f2(x) { var z = x, a = ++z; return z += a; }
function f3(x) { var a = (x -= 3); return x + a; } function f3(x) { var a = (x -= 3); return x + a; }
function f4(x) { var a = (x -= 3); return x + a; } function f4(x) { var a = (x -= 3); return x + a; }
function f5(x) { var v = (e1(), e2()), c = v = --x; return x - c; } function f5(x) { e1(); var v = e2(), c = v = --x; return x - c; }
function f6(x) { e1(), e2(); return --x - x; } function f6(x) { e1(), e2(); return --x - x; }
function f7(x) { var v = (e1(), e2()), c = v - x; return x - c; } function f7(x) { e1(); return x - (e2() - x); }
function f8(x) { var v = (e1(), e2()); return x - (v - x); } function f8(x) { e1(); return x - (e2() - x); }
function f9(x) { e1(); return e2() - x - x; } function f9(x) { e1(); return e2() - x - x; }
} }
} }
@@ -712,7 +712,7 @@ collapse_vars_misc1: {
options = { options = {
collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true,
comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true,
keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true, reduce_vars:true
} }
input: { input: {
function f0(o, a, h) { function f0(o, a, h) {
@@ -789,7 +789,7 @@ collapse_vars_repeated: {
options = { options = {
collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true,
comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true,
keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true, reduce_vars:true
} }
input: { input: {
function f1() { function f1() {
@@ -812,19 +812,17 @@ collapse_vars_repeated: {
} }
expect: { expect: {
function f1() { function f1() {
return -3 return -3;
} }
function f2(x) { function f2(x) {
return x return x;
} }
(function(x){ (function(x){
var a = "GOOD" + x, e = "BAD", e = a; console.log("GOOD!!");
console.log(e + "!"); })(),
})("!"),
(function(x){ (function(x){
var a = "GOOD" + x, e = "BAD" + x, e = a; console.log("GOOD!!");
console.log(e + "!"); })();
})("!");
} }
expect_stdout: true expect_stdout: true
} }
@@ -833,7 +831,7 @@ collapse_vars_closures: {
options = { options = {
collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true,
comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true,
keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true, reduce_vars:true
} }
input: { input: {
function constant_vars_can_be_replaced_in_any_scope() { function constant_vars_can_be_replaced_in_any_scope() {
@@ -923,7 +921,7 @@ collapse_vars_try: {
options = { options = {
collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true,
comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true,
keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true, reduce_vars:true
} }
input: { input: {
function f1() { function f1() {
@@ -1048,10 +1046,9 @@ collapse_vars_object: {
} }
expect: { expect: {
function f0(x, y) { function f0(x, y) {
var z = x + y;
return { return {
get b() { return 7; }, get b() { return 7; },
r: z r: x + y
}; };
} }
function f1(x, y) { function f1(x, y) {
@@ -1121,7 +1118,7 @@ collapse_vars_constants: {
options = { options = {
collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true,
comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true,
keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true, reduce_vars:true
} }
input: { input: {
function f1(x) { function f1(x) {
@@ -1159,7 +1156,7 @@ collapse_vars_arguments: {
collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true,
comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true,
keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true, keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true,
toplevel:true toplevel:true, reduce_vars:true
} }
input: { input: {
var outer = function() { var outer = function() {
@@ -1286,6 +1283,7 @@ collapse_vars_regexp: {
join_vars: true, join_vars: true,
cascade: true, cascade: true,
side_effects: true, side_effects: true,
reduce_vars: true,
} }
input: { input: {
function f1() { function f1() {
@@ -1319,8 +1317,8 @@ collapse_vars_regexp: {
}; };
} }
(function(){ (function(){
var result, s = "acdabcdeabbb", rx = /ab*/g; var result, rx = /ab*/g;
while (result = rx.exec(s)) while (result = rx.exec("acdabcdeabbb"))
console.log(result[0]); console.log(result[0]);
})(); })();
} }
@@ -1344,7 +1342,10 @@ issue_1537: {
issue_1562: { issue_1562: {
options = { options = {
collapse_vars: true, collapse_vars: true,
evaluate: true,
reduce_vars: true,
toplevel: true, toplevel: true,
unused: true,
} }
input: { input: {
var v = 1, B = 2; var v = 1, B = 2;
@@ -1363,14 +1364,11 @@ issue_1562: {
var v = 1; var v = 1;
for (v in objs) f(2); for (v in objs) f(2);
var x = 3; while(5) bar(10);
while(x + 2) bar(10);
var y = 4; do bar(20); while(6);
do bar(20); while(y + 2);
var z = 5; for (; f(7) ;) bar(30);
for (; f(z + 2) ;) bar(30);
} }
} }
@@ -1573,6 +1571,7 @@ var_side_effects_3: {
options = { options = {
collapse_vars: true, collapse_vars: true,
pure_getters: true, pure_getters: true,
unsafe: true,
} }
input: { input: {
var print = console.log.bind(console); var print = console.log.bind(console);
@@ -1591,3 +1590,599 @@ var_side_effects_3: {
} }
expect_stdout: true expect_stdout: true
} }
reduce_vars_assign: {
options = {
collapse_vars: true,
reduce_vars: true,
}
input: {
!function() {
var a = 1;
a = [].length,
console.log(a);
}();
}
expect: {
!function() {
var a = 1;
a = [].length,
console.log(a);
}();
}
expect_stdout: "0"
}
iife_1: {
options = {
collapse_vars: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var log = function(x) {
console.log(x);
}, foo = bar();
log(foo);
}
expect: {
(function(x) {
console.log(x);
})(bar());
}
}
iife_2: {
options = {
collapse_vars: true,
reduce_vars: false,
toplevel: true,
unused: false,
}
input: {
var foo = bar();
!function(x) {
console.log(x);
}(foo);
}
expect: {
!function(x) {
console.log(x);
}(bar());
}
}
var_defs: {
options = {
collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true,
comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true,
keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true
}
input: {
var f1 = function(x, y) {
var a, b, r = x + y, q = r * r, z = q - r, a = z, b = 7;
console.log(a + b);
};
f1("1", 0);
}
expect: {
var f1 = function(x, y) {
var r = x + y, a = r * r - r, b = 7;
console.log(a + b);
};
f1("1", 0);
}
expect_stdout: "97"
}
assignment: {
options = {
collapse_vars: true,
unused: true,
}
input: {
function f() {
var a;
a = x;
return a;
}
}
expect: {
function f() {
return x;
}
}
}
for_init: {
options = {
collapse_vars: true,
unused: true,
}
input: {
function f(x, y) {
var a = x;
var b = y;
for (a; b;);
}
}
expect: {
function f(x, y) {
var b = y;
for (x; b;);
}
}
}
switch_case: {
options = {
collapse_vars: true,
unused: true,
}
input: {
function f(x, y, z) {
var a = x();
var b = y();
var c = z;
switch (a) {
default: d();
case b: e();
case c: f();
}
}
}
expect: {
function f(x, y, z) {
var c = z;
switch (x()) {
default: d();
case y(): e();
case c: f();
}
}
}
}
issue_27: {
options = {
collapse_vars: true,
unused: true,
}
input: {
(function(jQuery) {
var $;
$ = jQuery;
$("body").addClass("foo");
})(jQuery);
}
expect: {
(function(jQuery) {
jQuery("body").addClass("foo");
})(jQuery);
}
}
modified: {
options = {
collapse_vars: true,
unused: true,
}
input: {
function f1(b) {
var a = b;
return b + a;
}
function f2(b) {
var a = b;
return b++ + a;
}
function f3(b) {
var a = b++;
return b + a;
}
function f4(b) {
var a = b++;
return b++ + a;
}
function f5(b) {
var a = function() {
return b;
}();
return b++ + a;
}
console.log(f1(1), f2(1), f3(1), f4(1), f5(1));
}
expect: {
function f1(b) {
return b + b;
}
function f2(b) {
var a = b;
return b++ + a;
}
function f3(b) {
var a = b++;
return b + a;
}
function f4(b) {
var a = b++;
return b++ + a;
}
function f5(b) {
var a = function() {
return b;
}();
return b++ + a;
}
console.log(f1(1), f2(1), f3(1), f4(1), f5(1));
}
expect_stdout: "2 2 3 3 2"
}
issue_1858: {
options = {
collapse_vars: true,
pure_getters: true,
unused: true,
}
input: {
console.log(function(x) {
var a = {}, b = a.b = x;
return a.b + b;
}(1));
}
expect: {
console.log(function(x) {
var a = {}, b = a.b = x;
return a.b + b;
}(1));
}
expect_stdout: "2"
}
anonymous_function: {
options = {
collapse_vars: true,
}
input: {
console.log(function f(a) {
f ^= 0;
return f * a;
}(1));
}
expect: {
console.log(function f(a) {
f ^= 0;
return f * a;
}(1));
}
expect_stdout: true
}
side_effects_property: {
options = {
collapse_vars: true,
}
input: {
var a = [];
var b = 0;
a[b++] = function() { return 42;};
var c = a[b++]();
console.log(c);
}
expect: {
var a = [];
var b = 0;
a[b++] = function() { return 42;};
var c = a[b++]();
console.log(c);
}
expect_stdout: true
}
undeclared: {
options = {
collapse_vars: true,
unused: true,
}
input: {
function f(x, y) {
var a;
a = x;
b = y;
return b + a;
}
}
expect: {
function f(x, y) {
var a;
a = x;
b = y;
return b + a;
}
}
}
ref_scope: {
options = {
collapse_vars: true,
unused: true,
}
input: {
console.log(function() {
var a = 1, b = 2, c = 3;
var a = c++, b = b /= a;
return function() {
return a;
}() + b;
}());
}
expect: {
console.log(function() {
var a = 1, b = 2, c = 3;
b = b /= a = c++;
return function() {
return a;
}() + b;
}());
}
expect_stdout: true
}
chained_1: {
options = {
collapse_vars: true,
}
input: {
var a = 2;
var a = 3 / a;
console.log(a);
}
expect: {
var a = 3 / (a = 2);
console.log(a);
}
expect_stdout: true
}
chained_2: {
options = {
collapse_vars: true,
}
input: {
var a;
var a = 2;
a = 3 / a;
console.log(a);
}
expect: {
var a;
a = 3 / (a = 2);
console.log(a);
}
expect_stdout: true
}
chained_3: {
options = {
collapse_vars: true,
unused: true,
}
input: {
console.log(function(a, b) {
var c = a, c = b;
b++;
return c;
}(1, 2));
}
expect: {
console.log(function(a, b) {
var c = a, c = b;
b++;
return c;
}(1, 2));
}
expect_stdout: "2"
}
boolean_binary_1: {
options = {
collapse_vars: true,
}
input: {
var a = 1;
a++;
(function() {} || a || 3).toString();
console.log(a);
}
expect: {
var a = 1;
a++;
(function() {} || a || 3).toString();
console.log(a);
}
expect_stdout: true
}
boolean_binary_2: {
options = {
collapse_vars: true,
}
input: {
var c = 0;
c += 1;
(function() {
c = 1 + c;
} || 9).toString();
console.log(c);
}
expect: {
var c = 0;
c += 1;
(function() {
c = 1 + c;
} || 9).toString();
console.log(c);
}
expect_stdout: true
}
inner_lvalues: {
options = {
collapse_vars: true,
unused: true,
}
input: {
var a, b = 10;
var a = (--b || a || 3).toString(), c = --b + -a;
console.log(null, a, b);
}
expect: {
var a, b = 10;
var a = (--b || a || 3).toString(), c = --b + -a;
console.log(null, a, b);
}
expect_stdout: true
}
double_def: {
options = {
collapse_vars: true,
}
input: {
var a = x, a = a && y;
a();
}
expect: {
var a = x;
(a = a && y)();
}
}
toplevel_single_reference: {
options = {
collapse_vars: true,
}
input: {
var a;
for (var b in x) {
var a = b;
b(a);
}
}
expect: {
var a;
for (var b in x)
b(a = b);
}
}
unused_orig: {
options = {
collapse_vars: true,
passes: 2,
reduce_vars: true,
unused: true,
}
input: {
var a = 1;
console.log(function(b) {
var a;
var c = b;
for (var d in c) {
var a = c[0];
return --b + a;
}
try {
} catch (e) {
--b + a;
}
a && a.NaN;
}([2]), a);
}
expect: {
var a = 1;
console.log(function(b) {
var c = b;
for (var d in c) {
var a = c[0];
return --b + a;
}
a && a.NaN;
}([2]), a);
}
expect_stdout: "3 1"
}
issue_315: {
options = {
collapse_vars: true,
evaluate: true,
keep_fargs: false,
reduce_vars: true,
sequences: true,
unused: true,
}
input: {
console.log(function(s) {
var w, _i, _len, _ref, _results;
_ref = s.trim().split(" ");
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
w = _ref[_i];
_results.push(w.toLowerCase());
}
return _results;
}("test"));
}
expect: {
console.log(function() {
var w, _i, _len, _ref, _results;
for (_results = [], _i = 0, _len = (_ref = "test".trim().split(" ")).length; _i < _len ; _i++)
w = _ref[_i], _results.push(w.toLowerCase());
return _results;
}());
}
expect_stdout: true
}
lvalues_def: {
options = {
collapse_vars: true,
side_effects: true,
unused: true,
}
input: {
var a = 0, b = 1;
var a = b++, b = +function() {}();
a && a[a++];
console.log(a, b);
}
expect: {
var a = 0, b = 1;
var a = b++, b = +void 0;
a && a[a++];
console.log(a, b);
}
expect_stdout: true
}
compound_assignment: {
options = {
collapse_vars: true,
}
input: {
var a;
a = 1;
a += a + 2;
console.log(a);
}
expect: {
var a;
a = 1;
a += a + 2;
console.log(a);
}
expect_stdout: "4"
}

View File

@@ -962,3 +962,56 @@ condition_symbol_matches_consequent: {
} }
expect_stdout: "3 7 true 4" expect_stdout: "3 7 true 4"
} }
delete_conditional_1: {
options = {
booleans: true,
conditionals: true,
evaluate: true,
side_effects: true,
}
input: {
console.log(delete (1 ? undefined : x));
console.log(delete (1 ? void 0 : x));
console.log(delete (1 ? Infinity : x));
console.log(delete (1 ? 1 / 0 : x));
console.log(delete (1 ? NaN : x));
console.log(delete (1 ? 0 / 0 : x));
}
expect: {
console.log(!0);
console.log(!0);
console.log(!0);
console.log(!0);
console.log(!0);
console.log(!0);
}
expect_stdout: true
}
delete_conditional_2: {
options = {
booleans: true,
conditionals: true,
evaluate: true,
keep_infinity: true,
side_effects: true,
}
input: {
console.log(delete (0 ? x : undefined));
console.log(delete (0 ? x : void 0));
console.log(delete (0 ? x : Infinity));
console.log(delete (0 ? x : 1 / 0));
console.log(delete (0 ? x : NaN));
console.log(delete (0 ? x : 0 / 0));
}
expect: {
console.log(!0);
console.log(!0);
console.log(!0);
console.log(!0);
console.log(!0);
console.log(!0);
}
expect_stdout: true
}

View File

@@ -935,7 +935,8 @@ issue_1715_3: {
try { try {
console; console;
} catch (a) { } catch (a) {
var a = x(); var a;
x();
} }
} }
f(); f();
@@ -974,3 +975,175 @@ issue_1715_4: {
} }
expect_stdout: "1" expect_stdout: "1"
} }
delete_assign_1: {
options = {
booleans: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
var a;
console.log(delete (a = undefined));
console.log(delete (a = void 0));
console.log(delete (a = Infinity));
console.log(delete (a = 1 / 0));
console.log(delete (a = NaN));
console.log(delete (a = 0 / 0));
}
expect: {
console.log((void 0, !0));
console.log((void 0, !0));
console.log((1 / 0, !0));
console.log((1 / 0, !0));
console.log((NaN, !0));
console.log((0 / 0, !0));
}
expect_stdout: true
}
delete_assign_2: {
options = {
booleans: true,
keep_infinity: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
var a;
console.log(delete (a = undefined));
console.log(delete (a = void 0));
console.log(delete (a = Infinity));
console.log(delete (a = 1 / 0));
console.log(delete (a = NaN));
console.log(delete (a = 0 / 0));
}
expect: {
console.log((void 0, !0));
console.log((void 0, !0));
console.log((Infinity, !0));
console.log((1 / 0, !0));
console.log((NaN, !0));
console.log((0 / 0, !0));
}
expect_stdout: true
}
drop_var: {
options = {
toplevel: true,
unused: true,
}
input: {
var a;
console.log(a, b);
var a = 1, b = 2;
console.log(a, b);
var a = 3;
console.log(a, b);
}
expect: {
console.log(a, b);
var a = 1, b = 2;
console.log(a, b);
a = 3;
console.log(a, b);
}
expect_stdout: [
"undefined undefined",
"1 2",
"3 2",
]
}
issue_1830_1: {
options = {
unused: true,
}
input: {
!function() {
L: for (var b = console.log(1); !1;) continue L;
}();
}
expect: {
!function() {
L: for (console.log(1); !1;) continue L;
}();
}
expect_stdout: "1"
}
issue_1830_2: {
options = {
unused: true,
}
input: {
!function() {
L: for (var a = 1, b = console.log(a); --a;) continue L;
}();
}
expect: {
!function() {
var a = 1;
L: for (console.log(a); --a;) continue L;
}();
}
expect_stdout: "1"
}
issue_1838: {
options = {
join_vars: true,
loops: true,
unused: true,
}
beautify = {
beautify: true,
}
input: {
function f() {
var b = a;
while (c);
}
}
expect_exact: [
"function f() {",
" for (a; c; ) ;",
"}",
]
}
var_catch_toplevel: {
options = {
conditionals: true,
negate_iife: true,
reduce_vars: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
function f() {
a--;
try {
a++;
} catch(a) {
if (a) var a;
var a = 10;
}
}
f();
}
expect: {
!function() {
a--;
try {
a++;
} catch(a) {
var a;
}
}();
}
}

View File

@@ -857,3 +857,135 @@ issue_1760_2: {
} }
expect_stdout: "Infinity" expect_stdout: "Infinity"
} }
delete_expr_1: {
options = {
booleans: true,
evaluate: true,
}
input: {
console.log(delete undefined);
console.log(delete void 0);
console.log(delete Infinity);
console.log(delete (1 / 0));
console.log(delete NaN);
console.log(delete (0 / 0));
}
expect: {
console.log(delete undefined);
console.log((void 0, !0));
console.log(delete Infinity);
console.log((1 / 0, !0));
console.log(delete NaN);
console.log((0 / 0, !0));
}
expect_stdout: true
}
delete_expr_2: {
options = {
booleans: true,
evaluate: true,
keep_infinity: true,
}
input: {
console.log(delete undefined);
console.log(delete void 0);
console.log(delete Infinity);
console.log(delete (1 / 0));
console.log(delete NaN);
console.log(delete (0 / 0));
}
expect: {
console.log(delete undefined);
console.log((void 0, !0));
console.log(delete Infinity);
console.log((1 / 0, !0));
console.log(delete NaN);
console.log((0 / 0, !0));
}
expect_stdout: true
}
delete_binary_1: {
options = {
booleans: true,
evaluate: true,
side_effects: true,
}
input: {
console.log(delete (true && undefined));
console.log(delete (true && void 0));
console.log(delete (true && Infinity));
console.log(delete (true && (1 / 0)));
console.log(delete (true && NaN));
console.log(delete (true && (0 / 0)));
}
expect: {
console.log(!0);
console.log(!0);
console.log(!0);
console.log(!0);
console.log(!0);
console.log(!0);
}
expect_stdout: true
}
delete_binary_2: {
options = {
booleans: true,
evaluate: true,
keep_infinity: true,
side_effects: true,
}
input: {
console.log(delete (false || undefined));
console.log(delete (false || void 0));
console.log(delete (false || Infinity));
console.log(delete (false || (1 / 0)));
console.log(delete (false || NaN));
console.log(delete (false || (0 / 0)));
}
expect: {
console.log(!0);
console.log(!0);
console.log(!0);
console.log(!0);
console.log(!0);
console.log(!0);
}
expect_stdout: true
}
Infinity_NaN_undefined_LHS: {
beautify = {
beautify: true,
}
input: {
function f() {
Infinity = Infinity;
++Infinity;
Infinity--;
NaN *= NaN;
++NaN;
NaN--;
undefined |= undefined;
++undefined;
undefined--;
}
}
expect_exact: [
"function f() {",
" Infinity = 1 / 0;",
" ++Infinity;",
" Infinity--;",
" NaN *= NaN;",
" ++NaN;",
" NaN--;",
" undefined |= void 0;",
" ++undefined;",
" undefined--;",
"}",
]
}

View File

@@ -93,3 +93,77 @@ issue_485_crashing_1530: {
this, void 0; this, void 0;
} }
} }
issue_1841_1: {
options = {
keep_fargs: false,
pure_getters: "strict",
reduce_vars: true,
unused: true,
}
input: {
var b = 10;
!function(arg) {
for (var key in "hi")
var n = arg.baz, n = [ b = 42 ];
}(--b);
console.log(b);
}
expect: {
var b = 10;
!function() {
for (var key in "hi")
b = 42;
}(--b);
console.log(b);
}
expect_exact: "42"
}
issue_1841_2: {
options = {
keep_fargs: false,
pure_getters: false,
reduce_vars: true,
unused: true,
}
input: {
var b = 10;
!function(arg) {
for (var key in "hi")
var n = arg.baz, n = [ b = 42 ];
}(--b);
console.log(b);
}
expect: {
var b = 10;
!function(arg) {
for (var key in "hi")
arg.baz, b = 42;
}(--b);
console.log(b);
}
expect_exact: "42"
}
function_returning_constant_literal: {
options = {
reduce_vars: true,
unsafe: true,
toplevel: true,
evaluate: true,
cascade: true,
unused: true,
}
input: {
function greeter() {
return { message: 'Hello there' };
}
var greeting = greeter();
console.log(greeting.message);
}
expect: {
console.log("Hello there");
}
expect_stdout: "Hello there"
}

View File

@@ -145,3 +145,18 @@ mixed: {
'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:129,8]', 'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:129,8]',
] ]
} }
issue_1801: {
options = {
booleans: true,
global_defs: {
"CONFIG.FOO.BAR": true,
},
}
input: {
console.log(CONFIG.FOO.BAR);
}
expect: {
console.log(!0);
}
}

View File

@@ -1,6 +1,6 @@
issue_1321_no_debug: { issue_1321_no_debug: {
mangle_props = { mangle_props = {
ignore_quoted: true keep_quoted: true
} }
input: { input: {
var x = {}; var x = {};
@@ -19,7 +19,7 @@ issue_1321_no_debug: {
issue_1321_debug: { issue_1321_debug: {
mangle_props = { mangle_props = {
ignore_quoted: true, keep_quoted: true,
debug: "" debug: ""
} }
input: { input: {
@@ -39,7 +39,7 @@ issue_1321_debug: {
issue_1321_with_quoted: { issue_1321_with_quoted: {
mangle_props = { mangle_props = {
ignore_quoted: false keep_quoted: false
} }
input: { input: {
var x = {}; var x = {};

View File

@@ -23,7 +23,7 @@ typeof_eq_undefined: {
typeof_eq_undefined_ie8: { typeof_eq_undefined_ie8: {
options = { options = {
comparisons: true, comparisons: true,
screw_ie8: false ie8: true,
} }
input: { input: {
var a = typeof b != "undefined"; var a = typeof b != "undefined";

View File

@@ -1,9 +1,9 @@
screw_ie8: { screw_ie8: {
options = { options = {
screw_ie8: true, ie8: false,
} }
mangle = { mangle = {
screw_ie8: true, ie8: false,
} }
input: { input: {
try { throw "foo"; } catch (x) { console.log(x); } try { throw "foo"; } catch (x) { console.log(x); }
@@ -16,10 +16,10 @@ screw_ie8: {
support_ie8: { support_ie8: {
options = { options = {
screw_ie8: false, ie8: true,
} }
mangle = { mangle = {
screw_ie8: false, ie8: true,
} }
input: { input: {
try { throw "foo"; } catch (x) { console.log(x); } try { throw "foo"; } catch (x) { console.log(x); }

View File

@@ -18,9 +18,7 @@ chained_evaluation_1: {
expect: { expect: {
(function() { (function() {
(function() { (function() {
var c; f(1).bar = 1;
c = f(1);
c.bar = 1;
})(); })();
})(); })();
} }
@@ -45,11 +43,9 @@ chained_evaluation_2: {
} }
expect: { expect: {
(function() { (function() {
var a = "long piece of string";
(function() { (function() {
var c; var b = "long piece of string";
c = f(a); f(b).bar = b;
c.bar = a;
})(); })();
})(); })();
} }

View File

@@ -35,11 +35,11 @@ f7: {
console.log(a, b); console.log(a, b);
} }
expect_exact: [ expect_exact: [
"var a = 100, b = 10;", "var b = 10;",
"", "",
"!function() {", "!function() {",
" for (;b = a, !1; ) ;", " for (;b = 100, !1; ) ;",
"}(), console.log(a, b);", "}(), console.log(100, b);",
] ]
expect_stdout: true expect_stdout: true
} }

View File

@@ -1,10 +1,10 @@
mangle_catch: { mangle_catch: {
options = { options = {
screw_ie8: true, ie8: false,
toplevel: false, toplevel: false,
} }
mangle = { mangle = {
screw_ie8: true, ie8: false,
toplevel: false, toplevel: false,
} }
input: { input: {
@@ -22,11 +22,11 @@ mangle_catch: {
mangle_catch_ie8: { mangle_catch_ie8: {
options = { options = {
screw_ie8: false, ie8: true,
toplevel: false, toplevel: false,
} }
mangle = { mangle = {
screw_ie8: false, ie8: true,
toplevel: false, toplevel: false,
} }
input: { input: {
@@ -44,11 +44,11 @@ mangle_catch_ie8: {
mangle_catch_var: { mangle_catch_var: {
options = { options = {
screw_ie8: true, ie8: false,
toplevel: false, toplevel: false,
} }
mangle = { mangle = {
screw_ie8: true, ie8: false,
toplevel: false, toplevel: false,
} }
input: { input: {
@@ -66,11 +66,11 @@ mangle_catch_var: {
mangle_catch_var_ie8: { mangle_catch_var_ie8: {
options = { options = {
screw_ie8: false, ie8: true,
toplevel: false, toplevel: false,
} }
mangle = { mangle = {
screw_ie8: false, ie8: true,
toplevel: false, toplevel: false,
} }
input: { input: {
@@ -88,11 +88,11 @@ mangle_catch_var_ie8: {
mangle_catch_toplevel: { mangle_catch_toplevel: {
options = { options = {
screw_ie8: true, ie8: false,
toplevel: true, toplevel: true,
} }
mangle = { mangle = {
screw_ie8: true, ie8: false,
toplevel: true, toplevel: true,
} }
input: { input: {
@@ -110,11 +110,11 @@ mangle_catch_toplevel: {
mangle_catch_ie8_toplevel: { mangle_catch_ie8_toplevel: {
options = { options = {
screw_ie8: false, ie8: true,
toplevel: true, toplevel: true,
} }
mangle = { mangle = {
screw_ie8: false, ie8: true,
toplevel: true, toplevel: true,
} }
input: { input: {
@@ -132,11 +132,11 @@ mangle_catch_ie8_toplevel: {
mangle_catch_var_toplevel: { mangle_catch_var_toplevel: {
options = { options = {
screw_ie8: true, ie8: false,
toplevel: true, toplevel: true,
} }
mangle = { mangle = {
screw_ie8: true, ie8: false,
toplevel: true, toplevel: true,
} }
input: { input: {
@@ -154,11 +154,11 @@ mangle_catch_var_toplevel: {
mangle_catch_var_ie8_toplevel: { mangle_catch_var_ie8_toplevel: {
options = { options = {
screw_ie8: false, ie8: true,
toplevel: true, toplevel: true,
} }
mangle = { mangle = {
screw_ie8: false, ie8: true,
toplevel: true, toplevel: true,
} }
input: { input: {
@@ -176,11 +176,11 @@ mangle_catch_var_ie8_toplevel: {
mangle_catch_redef_1: { mangle_catch_redef_1: {
options = { options = {
screw_ie8: true, ie8: false,
toplevel: false, toplevel: false,
} }
mangle = { mangle = {
screw_ie8: true, ie8: false,
toplevel: false, toplevel: false,
} }
input: { input: {
@@ -198,11 +198,11 @@ mangle_catch_redef_1: {
mangle_catch_redef_1_ie8: { mangle_catch_redef_1_ie8: {
options = { options = {
screw_ie8: false, ie8: true,
toplevel: false, toplevel: false,
} }
mangle = { mangle = {
screw_ie8: false, ie8: true,
toplevel: false, toplevel: false,
} }
input: { input: {
@@ -220,11 +220,11 @@ mangle_catch_redef_1_ie8: {
mangle_catch_redef_1_toplevel: { mangle_catch_redef_1_toplevel: {
options = { options = {
screw_ie8: true, ie8: false,
toplevel: true, toplevel: true,
} }
mangle = { mangle = {
screw_ie8: true, ie8: false,
toplevel: true, toplevel: true,
} }
input: { input: {
@@ -242,11 +242,11 @@ mangle_catch_redef_1_toplevel: {
mangle_catch_redef_1_ie8_toplevel: { mangle_catch_redef_1_ie8_toplevel: {
options = { options = {
screw_ie8: false, ie8: true,
toplevel: true, toplevel: true,
} }
mangle = { mangle = {
screw_ie8: false, ie8: true,
toplevel: true, toplevel: true,
} }
input: { input: {
@@ -264,11 +264,11 @@ mangle_catch_redef_1_ie8_toplevel: {
mangle_catch_redef_2: { mangle_catch_redef_2: {
options = { options = {
screw_ie8: true, ie8: false,
toplevel: false, toplevel: false,
} }
mangle = { mangle = {
screw_ie8: true, ie8: false,
toplevel: false, toplevel: false,
} }
input: { input: {
@@ -285,11 +285,11 @@ mangle_catch_redef_2: {
mangle_catch_redef_2_ie8: { mangle_catch_redef_2_ie8: {
options = { options = {
screw_ie8: false, ie8: true,
toplevel: false, toplevel: false,
} }
mangle = { mangle = {
screw_ie8: false, ie8: true,
toplevel: false, toplevel: false,
} }
input: { input: {
@@ -306,11 +306,11 @@ mangle_catch_redef_2_ie8: {
mangle_catch_redef_2_toplevel: { mangle_catch_redef_2_toplevel: {
options = { options = {
screw_ie8: true, ie8: false,
toplevel: true, toplevel: true,
} }
mangle = { mangle = {
screw_ie8: true, ie8: false,
toplevel: true, toplevel: true,
} }
input: { input: {
@@ -327,11 +327,11 @@ mangle_catch_redef_2_toplevel: {
mangle_catch_redef_2_ie8_toplevel: { mangle_catch_redef_2_ie8_toplevel: {
options = { options = {
screw_ie8: false, ie8: true,
toplevel: true, toplevel: true,
} }
mangle = { mangle = {
screw_ie8: false, ie8: true,
toplevel: true, toplevel: true,
} }
input: { input: {

View File

@@ -1,6 +1,6 @@
function_iife_catch: { function_iife_catch: {
mangle = { mangle = {
screw_ie8: true, ie8: false,
} }
input: { input: {
function f(n) { function f(n) {
@@ -21,7 +21,7 @@ function_iife_catch: {
function_iife_catch_ie8: { function_iife_catch_ie8: {
mangle = { mangle = {
screw_ie8: false, ie8: true,
} }
input: { input: {
function f(n) { function f(n) {
@@ -42,7 +42,7 @@ function_iife_catch_ie8: {
function_catch_catch: { function_catch_catch: {
mangle = { mangle = {
screw_ie8: true, ie8: false,
} }
input: { input: {
var o = 0; var o = 0;
@@ -70,7 +70,7 @@ function_catch_catch: {
function_catch_catch_ie8: { function_catch_catch_ie8: {
mangle = { mangle = {
screw_ie8: false, ie8: true,
} }
input: { input: {
var o = 0; var o = 0;

239
test/compress/issue-1770.js Normal file
View File

@@ -0,0 +1,239 @@
mangle_props: {
mangle_props = {}
input: {
var obj = {
undefined: 1,
NaN: 2,
Infinity: 3,
"-Infinity": 4,
null: 5,
};
console.log(
obj[void 0],
obj[undefined],
obj["undefined"],
obj[0/0],
obj[NaN],
obj["NaN"],
obj[1/0],
obj[Infinity],
obj["Infinity"],
obj[-1/0],
obj[-Infinity],
obj["-Infinity"],
obj[null],
obj["null"]
);
}
expect: {
var obj = {
undefined: 1,
NaN: 2,
Infinity: 3,
"-Infinity": 4,
null: 5,
};
console.log(
obj[void 0],
obj[void 0],
obj["undefined"],
obj[0/0],
obj[NaN],
obj["NaN"],
obj[1/0],
obj[1/0],
obj["Infinity"],
obj[-1/0],
obj[-1/0],
obj["-Infinity"],
obj[null],
obj["null"]
);
}
expect_stdout: "1 1 1 2 2 2 3 3 3 4 4 4 5 5"
}
numeric_literal: {
beautify = {
beautify: true,
}
mangle_props = {}
input: {
var obj = {
0: 0,
"-0": 1,
42: 2,
"42": 3,
0x25: 4,
"0x25": 5,
1E42: 6,
"1E42": 7,
"1e+42": 8,
};
console.log(obj[-0], obj[-""], obj["-0"]);
console.log(obj[42], obj["42"]);
console.log(obj[0x25], obj["0x25"], obj[37], obj["37"]);
console.log(obj[1E42], obj["1E42"], obj["1e+42"]);
}
expect_exact: [
'var obj = {',
' 0: 0,',
' "-0": 1,',
' 42: 2,',
' "42": 3,',
' 37: 4,',
' a: 5,',
' 1e42: 6,',
' b: 7,',
' "1e+42": 8',
'};',
'',
'console.log(obj[-0], obj[-""], obj["-0"]);',
'',
'console.log(obj[42], obj["42"]);',
'',
'console.log(obj[37], obj["a"], obj[37], obj["37"]);',
'',
'console.log(obj[1e42], obj["b"], obj["1e+42"]);',
]
expect_stdout: [
"0 0 1",
"3 3",
"4 5 4 4",
"8 7 8",
]
}
identifier: {
mangle_props = {}
input: {
var obj = {
abstract: 1,
boolean: 2,
byte: 3,
char: 4,
class: 5,
double: 6,
enum: 7,
export: 8,
extends: 9,
final: 10,
float: 11,
goto: 12,
implements: 13,
import: 14,
int: 15,
interface: 16,
let: 17,
long: 18,
native: 19,
package: 20,
private: 21,
protected: 22,
public: 23,
short: 24,
static: 25,
super: 26,
synchronized: 27,
this: 28,
throws: 29,
transient: 30,
volatile: 31,
yield: 32,
false: 33,
null: 34,
true: 35,
break: 36,
case: 37,
catch: 38,
const: 39,
continue: 40,
debugger: 41,
default: 42,
delete: 43,
do: 44,
else: 45,
finally: 46,
for: 47,
function: 48,
if: 49,
in: 50,
instanceof: 51,
new: 52,
return: 53,
switch: 54,
throw: 55,
try: 56,
typeof: 57,
var: 58,
void: 59,
while: 60,
with: 61,
};
}
expect: {
var obj = {
a: 1,
b: 2,
c: 3,
d: 4,
e: 5,
f: 6,
g: 7,
h: 8,
i: 9,
j: 10,
k: 11,
l: 12,
m: 13,
n: 14,
o: 15,
p: 16,
q: 17,
r: 18,
s: 19,
t: 20,
u: 21,
v: 22,
w: 23,
x: 24,
y: 25,
z: 26,
A: 27,
B: 28,
C: 29,
D: 30,
F: 31,
G: 32,
false: 33,
null: 34,
true: 35,
H: 36,
I: 37,
J: 38,
K: 39,
L: 40,
M: 41,
N: 42,
O: 43,
P: 44,
Q: 45,
R: 46,
S: 47,
T: 48,
U: 49,
V: 50,
W: 51,
X: 52,
Y: 53,
Z: 54,
$: 55,
_: 56,
aa: 57,
ba: 58,
ca: 59,
da: 60,
ea: 61,
};
}
}

View File

@@ -0,0 +1,15 @@
unary_prefix: {
options = {
evaluate: true,
reduce_vars: true,
unused: true,
}
input: {
console.log(function() {
var x = -(2 / 3);
return x;
}());
}
expect_exact: "console.log(-2/3);"
expect_stdout: true
}

134
test/compress/issue-1833.js Normal file
View File

@@ -0,0 +1,134 @@
iife_for: {
options = {
negate_iife: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
function f() {
function g() {
L: for (;;) break L;
}
g();
}
f();
}
expect: {
!function() {
!function() {
L: for (;;) break L;
}();
}();
}
}
iife_for_in: {
options = {
negate_iife: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
function f() {
function g() {
L: for (var a in x) break L;
}
g();
}
f();
}
expect: {
!function() {
!function() {
L: for (var a in x) break L;
}();
}();
}
}
iife_do: {
options = {
negate_iife: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
function f() {
function g() {
L: do {
break L;
} while (1);
}
g();
}
f();
}
expect: {
!function() {
!function() {
L: do {
break L;
} while (1);
}();
}();
}
}
iife_while: {
options = {
negate_iife: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
function f() {
function g() {
L: while (1) break L;
}
g();
}
f();
}
expect: {
!function() {
!function() {
L: while (1) break L;
}();
}();
}
}
label_do: {
options = {
evaluate: true,
loops: true,
}
input: {
L: do {
continue L;
} while (0);
}
expect: {
L: do {
continue L;
} while (0);
}
}
label_while: {
options = {
evaluate: true,
dead_code: true,
loops: true,
}
input: {
function f() {
L: while (0) continue L;
}
}
expect_exact: "function f(){L:;}"
}

View File

@@ -159,7 +159,7 @@ negate_iife_4: {
})(); })();
} }
expect: { expect: {
(function(){ return t })() ? console.log(true) : console.log(false), function(){ !function(){ return t }() ? console.log(false) : console.log(true), function(){
console.log("something"); console.log("something");
}(); }();
} }
@@ -183,7 +183,7 @@ negate_iife_5: {
})(); })();
} }
expect: { expect: {
(function(){ return t })() ? foo(true) : bar(false), function(){ !function(){ return t }() ? bar(false) : foo(true), function(){
console.log("something"); console.log("something");
}(); }();
} }
@@ -207,7 +207,7 @@ negate_iife_5_off: {
})(); })();
} }
expect: { expect: {
(function(){ return t })() ? foo(true) : bar(false), function(){ !function(){ return t }() ? bar(false) : foo(true), function(){
console.log("something"); console.log("something");
}(); }();
} }

View File

@@ -215,8 +215,7 @@ evaluate: {
a(); a();
for(;;) for(;;)
c(); c();
// rule disabled due to issue_1532 d();
do d(); while (false);
} }
} }
@@ -246,7 +245,7 @@ issue_1532: {
issue_186: { issue_186: {
beautify = { beautify = {
beautify: false, beautify: false,
screw_ie8: true, ie8: false,
} }
input: { input: {
var x = 3; var x = 3;
@@ -265,7 +264,7 @@ issue_186: {
issue_186_ie8: { issue_186_ie8: {
beautify = { beautify = {
beautify: false, beautify: false,
screw_ie8: false, ie8: true,
} }
input: { input: {
var x = 3; var x = 3;
@@ -284,7 +283,7 @@ issue_186_ie8: {
issue_186_beautify: { issue_186_beautify: {
beautify = { beautify = {
beautify: true, beautify: true,
screw_ie8: true, ie8: false,
} }
input: { input: {
var x = 3; var x = 3;
@@ -311,7 +310,7 @@ issue_186_beautify: {
issue_186_beautify_ie8: { issue_186_beautify_ie8: {
beautify = { beautify = {
beautify: true, beautify: true,
screw_ie8: false, ie8: true,
} }
input: { input: {
var x = 3; var x = 3;
@@ -341,7 +340,7 @@ issue_186_bracketize: {
beautify = { beautify = {
beautify: false, beautify: false,
bracketize: true, bracketize: true,
screw_ie8: true, ie8: false,
} }
input: { input: {
var x = 3; var x = 3;
@@ -361,7 +360,7 @@ issue_186_bracketize_ie8: {
beautify = { beautify = {
beautify: false, beautify: false,
bracketize: true, bracketize: true,
screw_ie8: false, ie8: true,
} }
input: { input: {
var x = 3; var x = 3;
@@ -381,7 +380,7 @@ issue_186_beautify_bracketize: {
beautify = { beautify = {
beautify: true, beautify: true,
bracketize: true, bracketize: true,
screw_ie8: true, ie8: false,
} }
input: { input: {
var x = 3; var x = 3;
@@ -413,7 +412,7 @@ issue_186_beautify_bracketize_ie8: {
beautify = { beautify = {
beautify: true, beautify: true,
bracketize: true, bracketize: true,
screw_ie8: false, ie8: true,
} }
input: { input: {
var x = 3; var x = 3;
@@ -458,3 +457,26 @@ issue_1648: {
} }
expect_exact: "function f(){for(x();1;);}" expect_exact: "function f(){for(x();1;);}"
} }
do_switch: {
options = {
evaluate: true,
loops: true,
}
input: {
do {
switch (a) {
case b:
continue;
}
} while (false);
}
expect: {
do {
switch (a) {
case b:
continue;
}
} while (false);
}
}

View File

@@ -25,11 +25,9 @@ negate_iife_2: {
negate_iife: true negate_iife: true
}; };
input: { input: {
(function(){ return {} })().x = 10; // should not transform this one
}
expect: {
(function(){ return {} })().x = 10; (function(){ return {} })().x = 10;
} }
expect_exact: "({}).x=10;"
} }
negate_iife_2_side_effects: { negate_iife_2_side_effects: {
@@ -38,11 +36,9 @@ negate_iife_2_side_effects: {
side_effects: true, side_effects: true,
} }
input: { input: {
(function(){ return {} })().x = 10; // should not transform this one
}
expect: {
(function(){ return {} })().x = 10; (function(){ return {} })().x = 10;
} }
expect_exact: "({}).x=10;"
} }
negate_iife_3: { negate_iife_3: {

View File

@@ -13,7 +13,7 @@ keep_properties: {
dot_properties: { dot_properties: {
options = { options = {
properties: true, properties: true,
screw_ie8: false ie8: true,
}; };
input: { input: {
a["foo"] = "bar"; a["foo"] = "bar";
@@ -36,7 +36,7 @@ dot_properties: {
dot_properties_es5: { dot_properties_es5: {
options = { options = {
properties: true, properties: true,
screw_ie8: true ie8: false,
}; };
input: { input: {
a["foo"] = "bar"; a["foo"] = "bar";
@@ -125,7 +125,7 @@ evaluate_string_length: {
mangle_properties: { mangle_properties: {
mangle_props = { mangle_props = {
ignore_quoted: false keep_quoted: false
}; };
input: { input: {
a["foo"] = "bar"; a["foo"] = "bar";
@@ -148,7 +148,7 @@ mangle_unquoted_properties: {
properties: false properties: false
} }
mangle_props = { mangle_props = {
ignore_quoted: true keep_quoted: true
} }
beautify = { beautify = {
beautify: false, beautify: false,
@@ -233,12 +233,12 @@ mangle_debug_suffix: {
} }
} }
mangle_debug_suffix_ignore_quoted: { mangle_debug_suffix_keep_quoted: {
options = { options = {
properties: false properties: false
} }
mangle_props = { mangle_props = {
ignore_quoted: true, keep_quoted: true,
debug: "XYZ", debug: "XYZ",
reserved: [] reserved: []
} }

View File

@@ -0,0 +1,121 @@
strict: {
options = {
pure_getters: "strict",
reduce_vars: false,
side_effects: true,
toplevel: true,
}
input: {
var a, b = null, c = {};
a.prop;
b.prop;
c.prop;
d.prop;
null.prop;
(void 0).prop;
undefined.prop;
}
expect: {
var a, b = null, c = {};
a.prop;
b.prop;
c.prop;
d.prop;
null.prop;
(void 0).prop;
(void 0).prop;
}
}
strict_reduce_vars: {
options = {
pure_getters: "strict",
reduce_vars: true,
side_effects: true,
toplevel: true,
}
input: {
var a, b = null, c = {};
a.prop;
b.prop;
c.prop;
d.prop;
null.prop;
(void 0).prop;
undefined.prop;
}
expect: {
var a, b = null, c = {};
a.prop;
b.prop;
d.prop;
null.prop;
(void 0).prop;
(void 0).prop;
}
}
unsafe: {
options = {
pure_getters: true,
reduce_vars: false,
side_effects: true,
toplevel: true,
}
input: {
var a, b = null, c = {};
a.prop;
b.prop;
c.prop;
d.prop;
null.prop;
(void 0).prop;
undefined.prop;
}
expect: {
var a, b = null, c = {};
d;
null.prop;
(void 0).prop;
(void 0).prop;
}
}
unsafe_reduce_vars: {
options = {
pure_getters: true,
reduce_vars: true,
side_effects: true,
toplevel: true,
}
input: {
var a, b = null, c = {};
a.prop;
b.prop;
c.prop;
d.prop;
null.prop;
(void 0).prop;
undefined.prop;
}
expect: {
var a, b = null, c = {};
d;
null.prop;
(void 0).prop;
(void 0).prop;
}
}
chained: {
options = {
pure_getters: "strict",
side_effects: true,
}
input: {
a.b.c;
}
expect: {
a.b.c;
}
}

View File

@@ -53,9 +53,7 @@ reduce_vars: {
console.log(-3); console.log(-3);
eval("console.log(a);"); eval("console.log(a);");
})(eval); })(eval);
(function() { "yes";
return "yes";
})();
console.log(2); console.log(2);
} }
expect_stdout: true expect_stdout: true
@@ -66,7 +64,7 @@ modified: {
conditionals : true, conditionals : true,
evaluate : true, evaluate : true,
reduce_vars : true, reduce_vars : true,
unused : true unused : true,
} }
input: { input: {
function f0() { function f0() {
@@ -136,12 +134,11 @@ modified: {
} }
function f2() { function f2() {
var b = 2; 3;
b = 3;
console.log(1 + b);
console.log(b + 3);
console.log(4); console.log(4);
console.log(1 + b + 3); console.log(6);
console.log(4);
console.log(7);
} }
function f3() { function f3() {
@@ -300,7 +297,7 @@ unsafe_evaluate_array: {
} }
} }
unsafe_evaluate_equality: { unsafe_evaluate_equality_1: {
options = { options = {
evaluate : true, evaluate : true,
reduce_vars : true, reduce_vars : true,
@@ -308,47 +305,62 @@ unsafe_evaluate_equality: {
unused : true unused : true
} }
input: { input: {
function f0(){ function f0() {
var a = {}; var a = {};
console.log(a === a); return a === a;
} }
function f1() {
function f1(){
var a = []; var a = [];
console.log(a === a); return a === a;
} }
console.log(f0(), f1());
}
expect: {
function f0() {
return true;
}
function f1() {
return true;
}
console.log(f0(), f1());
}
expect_stdout: true
}
function f2(){ unsafe_evaluate_equality_2: {
options = {
collapse_vars: true,
evaluate : true,
passes : 2,
reduce_vars : true,
unsafe : true,
unused : true
}
input: {
function f2() {
var a = {a:1, b:2}; var a = {a:1, b:2};
var b = a; var b = a;
var c = a; var c = a;
console.log(b === c); return b === c;
} }
function f3() {
function f3(){
var a = [1, 2, 3]; var a = [1, 2, 3];
var b = a; var b = a;
var c = a; var c = a;
console.log(b === c); return b === c;
} }
console.log(f2(), f3());
} }
expect: { expect: {
function f0(){ function f2() {
console.log(true); return true;
} }
function f3() {
function f1(){ return true;
console.log(true);
}
function f2(){
console.log(true);
}
function f3(){
console.log(true);
} }
console.log(f2(), f3());
} }
expect_stdout: true
} }
passes: { passes: {
@@ -375,12 +387,11 @@ passes: {
} }
expect: { expect: {
function f() { function f() {
var b = 2; 3;
b = 3;
console.log(1 + b);
console.log(b + 3);
console.log(4); console.log(4);
console.log(1 + b + 3); console.log(6);
console.log(4);
console.log(7);
} }
} }
} }
@@ -573,7 +584,7 @@ inner_var_label: {
} }
} }
inner_var_for: { inner_var_for_1: {
options = { options = {
evaluate: true, evaluate: true,
reduce_vars: true, reduce_vars: true,
@@ -602,6 +613,29 @@ inner_var_for: {
} }
} }
inner_var_for_2: {
options = {
evaluate: true,
reduce_vars: true,
unused: true,
}
input: {
!function() {
var a = 1;
for (var b = 1; --b;) var a = 2;
console.log(a);
}();
}
expect: {
!function() {
a = 1;
for (var b = 1; --b;) var a = 2;
console.log(a);
}();
}
expect_stdout: "1"
}
inner_var_for_in_1: { inner_var_for_in_1: {
options = { options = {
evaluate: true, evaluate: true,
@@ -1639,7 +1673,7 @@ redefine_arguments_1: {
return typeof arguments; return typeof arguments;
} }
function g() { function g() {
return"number"; return "number";
} }
function h(x) { function h(x) {
var arguments = x; var arguments = x;
@@ -1678,9 +1712,7 @@ redefine_arguments_2: {
console.log(function() { console.log(function() {
var arguments; var arguments;
return typeof arguments; return typeof arguments;
}(), function() { }(), "number", function(x) {
return"number";
}(), function(x) {
var arguments = x; var arguments = x;
return typeof arguments; return typeof arguments;
}()); }());
@@ -1789,9 +1821,7 @@ redefine_farg_2: {
console.log(function(a) { console.log(function(a) {
var a; var a;
return typeof a; return typeof a;
}([]), function() { }([]), "number",function(a, b) {
return "number";
}(),function(a, b) {
var a = b; var a = b;
return typeof a; return typeof a;
}([])); }([]));
@@ -1828,10 +1858,7 @@ redefine_farg_3: {
console.log(function(a) { console.log(function(a) {
var a; var a;
return typeof a; return typeof a;
}([]), "number", function(a) { }([]), "number", "undefined");
var a = void 0;
return typeof a;
}([]));
} }
expect_stdout: "object number undefined" expect_stdout: "object number undefined"
} }
@@ -1866,3 +1893,571 @@ delay_def: {
} }
expect_stdout: true expect_stdout: true
} }
booleans: {
options = {
booleans: true,
evaluate: true,
reduce_vars: true,
}
input: {
console.log(function(a) {
if (a != 0);
switch (a) {
case 0:
return "FAIL";
case false:
return "PASS";
}
}(false));
}
expect: {
console.log(function(a) {
if (!1);
switch (!1) {
case 0:
return "FAIL";
case !1:
return "PASS";
}
}(!1));
}
expect_stdout: "PASS"
}
side_effects_assign: {
options = {
evaluate: true,
reduce_vars: true,
sequences: true,
side_effects: true,
toplevel: true,
}
input: {
var a = typeof void (a && a.in == 1, 0);
console.log(a);
}
expect: {
var a = typeof void (a && a.in);
console.log(a);
}
expect_stdout: "undefined"
}
pure_getters_1: {
options = {
pure_getters: "strict",
reduce_vars: true,
side_effects: true,
toplevel: true,
}
input: {
try {
var a = (a.b, 2);
} catch (e) {}
console.log(a);
}
expect: {
try {
var a = (a.b, 2);
} catch (e) {}
console.log(a);
}
expect_stdout: "undefined"
}
pure_getters_2: {
options = {
pure_getters: "strict",
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var a;
var a = a && a.b;
}
expect: {
var a = a && a.b;
}
}
pure_getters_3: {
options = {
pure_getters: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var a;
var a = a && a.b;
}
expect: {
}
}
catch_var: {
options = {
booleans: true,
evaluate: true,
reduce_vars: true,
}
input: {
try {
throw {};
} catch (e) {
var e;
console.log(!!e);
}
}
expect: {
try {
throw {};
} catch (e) {
var e;
console.log(!!e);
}
}
expect_stdout: "true"
}
var_assign_1: {
options = {
evaluate: true,
reduce_vars: true,
sequences: true,
side_effects: true,
unused: true,
}
input: {
!function() {
var a;
a = 2;
console.log(a);
}();
}
expect: {
!function() {
console.log(2);
}();
}
expect_stdout: "2"
}
var_assign_2: {
options = {
evaluate: true,
reduce_vars: true,
sequences: true,
side_effects: true,
unused: true,
}
input: {
!function() {
var a;
if (a = 2) console.log(a);
}();
}
expect: {
!function() {
if (2) console.log(2);
}();
}
expect_stdout: "2"
}
var_assign_3: {
options = {
evaluate: true,
reduce_vars: true,
sequences: true,
side_effects: true,
unused: true,
}
input: {
!function() {
var a;
while (a = 2);
console.log(a);
}();
}
expect: {
!function() {
var a;
while (a = 2);
console.log(a);
}();
}
}
var_assign_4: {
options = {
evaluate: true,
reduce_vars: true,
sequences: true,
side_effects: true,
unused: true,
}
input: {
!function a() {
a = 2;
console.log(a);
}();
}
expect: {
!function a() {
a = 2,
console.log(a);
}();
}
}
var_assign_5: {
options = {
evaluate: true,
reduce_vars: true,
sequences: true,
side_effects: true,
unused: true,
}
input: {
!function() {
var a;
!function(b) {
a = 2;
console.log(a, b);
}(a);
}();
}
expect: {
!function() {
var a;
!function(b) {
a = 2,
console.log(a, b);
}(a);
}();
}
expect_stdout: "2 undefined"
}
var_assign_6: {
options = {
evaluate: true,
reduce_vars: true,
unused: true,
}
input: {
!function() {
var a = function(){}(a = 1);
console.log(a);
}();
}
expect: {
!function() {
var a = function(){}(a = 1);
console.log(a);
}();
}
expect_stdout: "undefined"
}
immutable: {
options = {
evaluate: true,
reduce_vars: true,
unused: true,
}
input: {
!function() {
var a = "test";
console.log(a.indexOf("e"));
}();
}
expect: {
!function() {
console.log("test".indexOf("e"));
}();
}
expect_stdout: "1"
}
issue_1814_1: {
options = {
evaluate: true,
reduce_vars: true,
unused: true,
}
input: {
const a = 42;
!function() {
var b = a;
!function(a) {
console.log(a++, b);
}(0);
}();
}
expect: {
const a = 42;
!function() {
!function(a) {
console.log(a++, 42);
}(0);
}();
}
expect_stdout: "0 42"
}
issue_1814_2: {
options = {
evaluate: true,
reduce_vars: true,
unused: true,
}
input: {
const a = "32";
!function() {
var b = a + 1;
!function(a) {
console.log(a++, b);
}(0);
}();
}
expect: {
const a = "32";
!function() {
!function(a) {
console.log(a++, "321");
}(0);
}();
}
expect_stdout: "0 '321'"
}
try_abort: {
options = {
evaluate: true,
reduce_vars: true,
unused: true,
}
input: {
!function() {
try {
var a = 1;
throw "";
var b = 2;
} catch (e) {
}
console.log(a, b);
}();
}
expect: {
!function() {
try {
var a = 1;
throw "";
var b = 2;
} catch (e) {
}
console.log(a, b);
}();
}
expect_stdout: "1 undefined"
}
boolean_binary_assign: {
options = {
evaluate: true,
reduce_vars: true,
unused: true,
}
input: {
!function() {
var a;
void 0 && (a = 1);
console.log(a);
}();
}
expect: {
!function() {
var a;
void 0;
console.log(a);
}();
}
expect_stdout: "undefined"
}
cond_assign: {
options = {
evaluate: true,
reduce_vars: true,
unused: true,
}
input: {
!function() {
var a;
void 0 ? (a = 1) : 0;
console.log(a);
}();
}
expect: {
!function() {
var a;
void 0 ? (a = 1) : 0;
console.log(a);
}();
}
expect_stdout: "undefined"
}
iife_assign: {
options = {
evaluate: true,
reduce_vars: true,
unused: true,
}
input: {
!function() {
var a = 1, b = 0;
!function() {
b++;
return;
a = 2;
}();
console.log(a);
}();
}
expect: {
!function() {
var a = 1, b = 0;
!function() {
b++;
return;
a = 2;
}();
console.log(a);
}();
}
expect_stdout: "1"
}
issue_1850_1: {
options = {
reduce_vars: true,
toplevel: false,
unused: true,
}
input: {
function f() {
console.log(a, a, a);
}
var a = 1;
f();
}
expect: {
function f() {
console.log(a, a, a);
}
var a = 1;
f();
}
expect_stdout: true
}
issue_1850_2: {
options = {
reduce_vars: true,
toplevel: "funcs",
unused: true,
}
input: {
function f() {
console.log(a, a, a);
}
var a = 1;
f();
}
expect: {
var a = 1;
(function() {
console.log(a, a, a);
})();
}
expect_stdout: true
}
issue_1850_3: {
options = {
reduce_vars: true,
toplevel: "vars",
unused: true,
}
input: {
function f() {
console.log(a, a, a);
}
var a = 1;
f();
}
expect: {
function f() {
console.log(a, a, a);
}
var a = 1;
f();
}
expect_stdout: true
}
issue_1850_4: {
options = {
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
function f() {
console.log(a, a, a);
}
var a = 1;
f();
}
expect: {
var a = 1;
(function() {
console.log(a, a, a);
})();
}
expect_stdout: true
}
issue_1865: {
options = {
evaluate: true,
reduce_vars: true,
unsafe: true,
}
input: {
function f(some) {
some.thing = false;
}
console.log(function() {
var some = { thing: true };
f(some);
return some.thing;
}());
}
expect: {
function f(some) {
some.thing = false;
}
console.log(function() {
var some = { thing: true };
f(some);
return some.thing;
}());
}
expect_stdout: true
}

View File

@@ -1,9 +1,9 @@
do_screw: { do_screw: {
options = { options = {
screw_ie8: true, ie8: false,
} }
beautify = { beautify = {
screw_ie8: true, ie8: false,
ascii_only: true, ascii_only: true,
} }
input: { input: {
@@ -14,10 +14,10 @@ do_screw: {
dont_screw: { dont_screw: {
options = { options = {
screw_ie8: false, ie8: true,
} }
beautify = { beautify = {
screw_ie8: false, ie8: true,
ascii_only: true, ascii_only: true,
} }
input: { input: {
@@ -28,7 +28,7 @@ dont_screw: {
do_screw_constants: { do_screw_constants: {
options = { options = {
screw_ie8: true, ie8: false,
} }
input: { input: {
f(undefined, Infinity); f(undefined, Infinity);
@@ -38,7 +38,7 @@ do_screw_constants: {
dont_screw_constants: { dont_screw_constants: {
options = { options = {
screw_ie8: false, ie8: true,
} }
input: { input: {
f(undefined, Infinity); f(undefined, Infinity);
@@ -47,9 +47,15 @@ dont_screw_constants: {
} }
do_screw_try_catch: { do_screw_try_catch: {
options = { screw_ie8: true }; options = {
mangle = { screw_ie8: true }; ie8: false,
beautify = { screw_ie8: true }; }
mangle = {
ie8: false,
}
beautify = {
ie8: false,
}
input: { input: {
good = function(e){ good = function(e){
return function(error){ return function(error){
@@ -75,9 +81,15 @@ do_screw_try_catch: {
} }
dont_screw_try_catch: { dont_screw_try_catch: {
options = { screw_ie8: false }; options = {
mangle = { screw_ie8: false }; ie8: true,
beautify = { screw_ie8: false }; }
mangle = {
ie8: true,
}
beautify = {
ie8: true,
}
input: { input: {
bad = function(e){ bad = function(e){
return function(error){ return function(error){
@@ -103,9 +115,15 @@ dont_screw_try_catch: {
} }
do_screw_try_catch_undefined: { do_screw_try_catch_undefined: {
options = { screw_ie8: true }; options = {
mangle = { screw_ie8: true }; ie8: false,
beautify = { screw_ie8: true }; }
mangle = {
ie8: false,
}
beautify = {
ie8: false,
}
input: { input: {
function a(b){ function a(b){
try { try {
@@ -132,9 +150,15 @@ do_screw_try_catch_undefined: {
} }
dont_screw_try_catch_undefined: { dont_screw_try_catch_undefined: {
options = { screw_ie8: false }; options = {
mangle = { screw_ie8: false }; ie8: true,
beautify = { screw_ie8: false }; }
mangle = {
ie8: true,
}
beautify = {
ie8: true,
}
input: { input: {
function a(b){ function a(b){
try { try {
@@ -164,11 +188,11 @@ reduce_vars: {
options = { options = {
evaluate: true, evaluate: true,
reduce_vars: true, reduce_vars: true,
screw_ie8: false, ie8: true,
unused: true, unused: true,
} }
mangle = { mangle = {
screw_ie8: false, ie8: true,
} }
input: { input: {
function f() { function f() {
@@ -196,10 +220,10 @@ reduce_vars: {
issue_1586_1: { issue_1586_1: {
options = { options = {
screw_ie8: false, ie8: true,
} }
mangle = { mangle = {
screw_ie8: false, ie8: true,
} }
input: { input: {
function f() { function f() {
@@ -215,10 +239,10 @@ issue_1586_1: {
issue_1586_2: { issue_1586_2: {
options = { options = {
screw_ie8: true, ie8: false,
} }
mangle = { mangle = {
screw_ie8: true, ie8: false,
} }
input: { input: {
function f() { function f() {

View File

@@ -460,9 +460,253 @@ issue_1758: {
console.log(function(c) { console.log(function(c) {
var undefined = 42; var undefined = 42;
return function() { return function() {
return c--, c--, c.toString(), void 0; return c--, c--, void c.toString();
}(); }();
}()); }());
} }
expect_stdout: "undefined" expect_stdout: "undefined"
} }
delete_seq_1: {
options = {
booleans: true,
side_effects: true,
}
input: {
console.log(delete (1, undefined));
console.log(delete (1, void 0));
console.log(delete (1, Infinity));
console.log(delete (1, 1 / 0));
console.log(delete (1, NaN));
console.log(delete (1, 0 / 0));
}
expect: {
console.log(!0);
console.log(!0);
console.log(!0);
console.log(!0);
console.log(!0);
console.log(!0);
}
expect_stdout: true
}
delete_seq_2: {
options = {
booleans: true,
side_effects: true,
}
input: {
console.log(delete (1, 2, undefined));
console.log(delete (1, 2, void 0));
console.log(delete (1, 2, Infinity));
console.log(delete (1, 2, 1 / 0));
console.log(delete (1, 2, NaN));
console.log(delete (1, 2, 0 / 0));
}
expect: {
console.log(!0);
console.log(!0);
console.log(!0);
console.log(!0);
console.log(!0);
console.log(!0);
}
expect_stdout: true
}
delete_seq_3: {
options = {
booleans: true,
keep_infinity: true,
side_effects: true,
}
input: {
console.log(delete (1, 2, undefined));
console.log(delete (1, 2, void 0));
console.log(delete (1, 2, Infinity));
console.log(delete (1, 2, 1 / 0));
console.log(delete (1, 2, NaN));
console.log(delete (1, 2, 0 / 0));
}
expect: {
console.log(!0);
console.log(!0);
console.log(!0);
console.log(!0);
console.log(!0);
console.log(!0);
}
expect_stdout: true
}
delete_seq_4: {
options = {
booleans: true,
sequences: true,
side_effects: true,
}
input: {
function f() {}
console.log(delete (f(), undefined));
console.log(delete (f(), void 0));
console.log(delete (f(), Infinity));
console.log(delete (f(), 1 / 0));
console.log(delete (f(), NaN));
console.log(delete (f(), 0 / 0));
}
expect: {
function f() {}
console.log((f(), !0)),
console.log((f(), !0)),
console.log((f(), !0)),
console.log((f(), !0)),
console.log((f(), !0)),
console.log((f(), !0));
}
expect_stdout: true
}
delete_seq_5: {
options = {
booleans: true,
keep_infinity: true,
sequences: true,
side_effects: true,
}
input: {
function f() {}
console.log(delete (f(), undefined));
console.log(delete (f(), void 0));
console.log(delete (f(), Infinity));
console.log(delete (f(), 1 / 0));
console.log(delete (f(), NaN));
console.log(delete (f(), 0 / 0));
}
expect: {
function f() {}
console.log((f(), !0)),
console.log((f(), !0)),
console.log((f(), !0)),
console.log((f(), !0)),
console.log((f(), !0)),
console.log((f(), !0));
}
expect_stdout: true
}
delete_seq_6: {
options = {
booleans: true,
side_effects: true,
}
input: {
var a;
console.log(delete (1, a));
}
expect: {
var a;
console.log(!0);
}
expect_stdout: true
}
side_effects: {
options = {
sequences: true,
side_effects: true,
}
input: {
0, a(), 1, b(), 2, c(), 3;
}
expect: {
a(), b(), c();
}
}
side_effects_cascade_1: {
options = {
cascade: true,
conditionals: true,
sequences: true,
side_effects: true,
}
input: {
function f(a, b) {
a -= 42;
if (a < 0) a = 0;
b.a = a;
}
}
expect: {
function f(a, b) {
(a -= 42) < 0 && (a = 0), b.a = a;
}
}
}
side_effects_cascade_2: {
options = {
cascade: true,
side_effects: true,
}
input: {
function f(a, b) {
b = a,
!a + (b += a) || (b += a),
b = a,
b;
}
}
expect: {
function f(a, b) {
b = a,
!a + (b += a) || (b += a),
b = a;
}
}
}
side_effects_cascade_3: {
options = {
cascade: true,
conditionals: true,
side_effects: true,
}
input: {
function f(a, b) {
"foo" ^ (b += a),
b ? false : (b = a) ? -1 : (b -= a) - (b ^= a),
a-- || !a,
a;
}
}
expect: {
function f(a, b) {
!(b += a) && ((b = a) || (b -= a, b ^= a)),
--a;
}
}
}
issue_27: {
options = {
cascade: true,
passes: 2,
sequences: true,
side_effects: true,
unused: true,
}
input: {
(function(jQuery) {
var $;
$ = jQuery;
$("body").addClass("foo");
})(jQuery);
}
expect: {
(function(jQuery) {
jQuery("body").addClass("foo");
})(jQuery);
}
}

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

@@ -0,0 +1 @@
++null

View File

@@ -0,0 +1,8 @@
function f() {
const a;
}
function g() {
"use strict";
const a;
}

View File

@@ -0,0 +1,14 @@
function f(x) {
delete 42;
delete (0, x);
delete null;
delete x;
}
function g(x) {
"use strict";
delete 42;
delete (0, x);
delete null;
delete x;
}

View File

@@ -0,0 +1 @@
a.=

View File

@@ -0,0 +1 @@
%.a;

View File

@@ -0,0 +1 @@
a./();

View File

@@ -0,0 +1,6 @@
function f(arguments) {
}
function g(arguments) {
"use strict";
}

View File

@@ -0,0 +1,6 @@
function arguments() {
}
function eval() {
"use strict";
}

View File

@@ -0,0 +1,6 @@
!function eval() {
}();
!function arguments() {
"use strict";
}();

View File

@@ -0,0 +1 @@
console.log({%: 1});

View File

@@ -0,0 +1,8 @@
function f() {
try {} catch (eval) {}
}
function g() {
"use strict";
try {} catch (eval) {}
}

View File

@@ -0,0 +1,8 @@
function f() {
var eval;
}
function g() {
"use strict";
var eval;
}

View File

@@ -12,7 +12,7 @@ if (typeof phantom == "undefined") {
}); });
var args = process.argv.slice(2); var args = process.argv.slice(2);
if (!args.length) { if (!args.length) {
args.push("-mc", "warnings=false"); args.push("-mc");
} }
args.push("--stats"); args.push("--stats");
var child_process = require("child_process"); var child_process = require("child_process");

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

@@ -2,6 +2,10 @@ var assert = require("assert");
var exec = require("child_process").exec; var exec = require("child_process").exec;
var readFileSync = require("fs").readFileSync; var readFileSync = require("fs").readFileSync;
function read(path) {
return readFileSync(path, "utf8");
}
describe("bin/uglifyjs", function () { describe("bin/uglifyjs", function () {
var uglifyjscmd = '"' + process.argv[0] + '" bin/uglifyjs'; var uglifyjscmd = '"' + process.argv[0] + '" bin/uglifyjs';
it("should produce a functional build when using --self", function (done) { it("should produce a functional build when using --self", function (done) {
@@ -15,12 +19,12 @@ 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();
}); });
}); });
it("Should be able to filter comments correctly with `--comment all`", function (done) { it("Should be able to filter comments correctly with `--comments all`", function (done) {
var command = uglifyjscmd + ' test/input/comments/filter.js --comments all'; var command = uglifyjscmd + ' test/input/comments/filter.js --comments all';
exec(command, function (err, stdout) { exec(command, function (err, stdout) {
@@ -50,8 +54,8 @@ describe("bin/uglifyjs", function () {
done(); done();
}); });
}); });
it("Should append source map to output when using --source-map-inline", function (done) { it("Should append source map to output when using --source-map url=inline", function (done) {
var command = uglifyjscmd + ' test/input/issue-1323/sample.js --source-map-inline'; var command = uglifyjscmd + " test/input/issue-1323/sample.js --source-map url=inline";
exec(command, function (err, stdout) { exec(command, function (err, stdout) {
if (err) throw err; if (err) throw err;
@@ -61,7 +65,7 @@ describe("bin/uglifyjs", function () {
done(); done();
}); });
}); });
it("should not append source map to output when not using --source-map-inline", function (done) { it("should not append source map to output when not using --source-map url=inline", function (done) {
var command = uglifyjscmd + ' test/input/issue-1323/sample.js'; var command = uglifyjscmd + ' test/input/issue-1323/sample.js';
exec(command, function (err, stdout) { exec(command, function (err, stdout) {
@@ -137,7 +141,7 @@ describe("bin/uglifyjs", function () {
exec(command, function (err, stdout) { exec(command, function (err, stdout) {
if (err) throw err; if (err) throw err;
assert.strictEqual(stdout, readFileSync("test/input/issue-1482/default.js", "utf8")); assert.strictEqual(stdout, read("test/input/issue-1482/default.js"));
done(); done();
}); });
}); });
@@ -147,55 +151,59 @@ describe("bin/uglifyjs", function () {
exec(command, function (err, stdout) { exec(command, function (err, stdout) {
if (err) throw err; if (err) throw err;
assert.strictEqual(stdout, readFileSync("test/input/issue-1482/bracketize.js", "utf8")); assert.strictEqual(stdout, read("test/input/issue-1482/bracketize.js"));
done(); done();
}); });
}); });
it("Should process inline source map", function(done) { it("Should process inline source map", function(done) {
var command = uglifyjscmd + ' test/input/issue-520/input.js -mc toplevel --in-source-map inline --source-map-inline'; var command = uglifyjscmd + " test/input/issue-520/input.js -mc toplevel --source-map content=inline,url=inline";
exec(command, function (err, stdout) { exec(command, function (err, stdout) {
if (err) throw err; if (err) throw err;
assert.strictEqual(stdout, readFileSync("test/input/issue-520/output.js", "utf8")); assert.strictEqual(stdout, read("test/input/issue-520/output.js"));
done(); done();
}); });
}); });
it("Should warn for missing inline source map", function(done) { it("Should warn for missing inline source map", function(done) {
var command = uglifyjscmd + ' test/input/issue-1323/sample.js --in-source-map inline'; var command = uglifyjscmd + " test/input/issue-1323/sample.js --source-map content=inline,url=inline";
exec(command, function (err, stdout, stderr) { exec(command, function (err, stdout, stderr) {
if (err) throw err; if (err) throw err;
assert.strictEqual(stdout, "var bar=function(){function foo(bar){return bar}return foo}();\n"); assert.strictEqual(stdout, [
"var bar=function(){function foo(bar){return bar}return foo}();",
"//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInRlc3QvaW5wdXQvaXNzdWUtMTMyMy9zYW1wbGUuanMiXSwibmFtZXMiOlsiYmFyIiwiZm9vIl0sIm1hcHBpbmdzIjoiQUFBQSxHQUFJQSxLQUFNLFdBQ04sUUFBU0MsS0FBS0QsS0FDVixNQUFPQSxLQUdYLE1BQU9DIn0=",
"",
].join("\n"));
assert.strictEqual(stderr, "WARN: inline source map not found\n"); assert.strictEqual(stderr, "WARN: inline source map not found\n");
done(); done();
}); });
}); });
it("Should fail with multiple input and inline source map", function(done) { it("Should fail with multiple input and inline source map", function(done) {
var command = uglifyjscmd + ' test/input/issue-520/input.js test/input/issue-520/output.js --in-source-map inline --source-map-inline'; var command = uglifyjscmd + " test/input/issue-520/input.js test/input/issue-520/output.js --source-map content=inline,url=inline";
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, "ERROR: inline source map only works with singular input\n");
done(); done();
}); });
}); });
it("Should fail with acorn and inline source map", function(done) { it("Should fail with acorn and inline source map", function(done) {
var command = uglifyjscmd + ' test/input/issue-520/input.js --in-source-map inline --source-map-inline --acorn'; var command = uglifyjscmd + " test/input/issue-520/input.js --source-map content=inline,url=inline -p acorn";
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 built-in parser\n"); assert.strictEqual(stderr, "ERROR: inline source map only works with built-in parser\n");
done(); done();
}); });
}); });
it("Should fail with SpiderMonkey and inline source map", function(done) { it("Should fail with SpiderMonkey and inline source map", function(done) {
var command = uglifyjscmd + ' test/input/issue-520/input.js --in-source-map inline --source-map-inline --spidermonkey'; var command = uglifyjscmd + " test/input/issue-520/input.js --source-map content=inline,url=inline -p spidermonkey";
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 built-in parser\n"); assert.strictEqual(stderr, "ERROR: inline source map only works with built-in parser\n");
done(); done();
}); });
}); });
@@ -208,7 +216,7 @@ describe("bin/uglifyjs", function () {
assert.strictEqual(lines[0], "Parse error at test/input/invalid/simple.js:1,12"); assert.strictEqual(lines[0], "Parse error at test/input/invalid/simple.js:1,12");
assert.strictEqual(lines[1], "function f(a{}"); assert.strictEqual(lines[1], "function f(a{}");
assert.strictEqual(lines[2], " ^"); assert.strictEqual(lines[2], " ^");
assert.strictEqual(lines[3], "SyntaxError: Unexpected token punc «{», expected punc «,»"); assert.strictEqual(lines[3], "ERROR: Unexpected token punc «{», expected punc «,»");
done(); done();
}); });
}); });
@@ -221,7 +229,7 @@ describe("bin/uglifyjs", function () {
assert.strictEqual(lines[0], "Parse error at test/input/invalid/tab.js:1,12"); assert.strictEqual(lines[0], "Parse error at test/input/invalid/tab.js:1,12");
assert.strictEqual(lines[1], "\t\tfoo(\txyz, 0abc);"); assert.strictEqual(lines[1], "\t\tfoo(\txyz, 0abc);");
assert.strictEqual(lines[2], "\t\t \t ^"); assert.strictEqual(lines[2], "\t\t \t ^");
assert.strictEqual(lines[3], "SyntaxError: Invalid syntax: 0abc"); assert.strictEqual(lines[3], "ERROR: Invalid syntax: 0abc");
done(); done();
}); });
}); });
@@ -234,7 +242,7 @@ describe("bin/uglifyjs", function () {
assert.strictEqual(lines[0], "Parse error at test/input/invalid/eof.js:2,0"); assert.strictEqual(lines[0], "Parse error at test/input/invalid/eof.js:2,0");
assert.strictEqual(lines[1], "foo, bar("); assert.strictEqual(lines[1], "foo, bar(");
assert.strictEqual(lines[2], " ^"); assert.strictEqual(lines[2], " ^");
assert.strictEqual(lines[3], "SyntaxError: Unexpected token: eof (undefined)"); assert.strictEqual(lines[3], "ERROR: Unexpected token: eof (undefined)");
done(); done();
}); });
}); });
@@ -247,20 +255,10 @@ describe("bin/uglifyjs", function () {
assert.strictEqual(lines[0], "Parse error at test/input/invalid/loop-no-body.js:2,0"); assert.strictEqual(lines[0], "Parse error at test/input/invalid/loop-no-body.js:2,0");
assert.strictEqual(lines[1], "for (var i = 0; i < 1; i++) "); assert.strictEqual(lines[1], "for (var i = 0; i < 1; i++) ");
assert.strictEqual(lines[2], " ^"); assert.strictEqual(lines[2], " ^");
assert.strictEqual(lines[3], "SyntaxError: Unexpected token: eof (undefined)"); assert.strictEqual(lines[3], "ERROR: Unexpected token: eof (undefined)");
done(); done();
}); });
}); });
it("Should support hyphen as shorthand", function(done) {
var command = uglifyjscmd + ' test/input/issue-1431/sample.js -m keep-fnames=true';
exec(command, function (err, stdout) {
if (err) throw err;
assert.strictEqual(stdout, "function f(r){return function(){function n(n){return n*n}return r(n)}}function g(n){return n(1)+n(2)}console.log(f(g)()==5);\n");
done();
});
});
it("Should throw syntax error (5--)", function(done) { it("Should throw syntax error (5--)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/assign_1.js'; var command = uglifyjscmd + ' test/input/invalid/assign_1.js';
@@ -271,7 +269,7 @@ describe("bin/uglifyjs", function () {
"Parse error at test/input/invalid/assign_1.js:1,18", "Parse error at test/input/invalid/assign_1.js:1,18",
"console.log(1 || 5--);", "console.log(1 || 5--);",
" ^", " ^",
"SyntaxError: Invalid use of -- operator" "ERROR: Invalid use of -- operator"
].join("\n")); ].join("\n"));
done(); done();
}); });
@@ -286,7 +284,7 @@ describe("bin/uglifyjs", function () {
"Parse error at test/input/invalid/assign_2.js:1,32", "Parse error at test/input/invalid/assign_2.js:1,32",
"console.log(2 || (Math.random() /= 2));", "console.log(2 || (Math.random() /= 2));",
" ^", " ^",
"SyntaxError: Invalid assignment" "ERROR: Invalid assignment"
].join("\n")); ].join("\n"));
done(); done();
}); });
@@ -298,12 +296,228 @@ describe("bin/uglifyjs", function () {
assert.ok(err); assert.ok(err);
assert.strictEqual(stdout, ""); assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [ assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/assign_3.js:1,18", "Parse error at test/input/invalid/assign_3.js:1,17",
"console.log(3 || ++this);", "console.log(3 || ++this);",
" ^", " ^",
"SyntaxError: Invalid use of ++ operator" "ERROR: Invalid use of ++ operator"
].join("\n")); ].join("\n"));
done(); done();
}); });
}); });
it("Should throw syntax error (++null)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/assign_4.js';
exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/assign_4.js:1,0",
"++null",
"^",
"ERROR: Invalid use of ++ operator"
].join("\n"));
done();
});
});
it("Should throw syntax error (a.=)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/dot_1.js';
exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/dot_1.js:1,2",
"a.=",
" ^",
"ERROR: Unexpected token: operator (=)"
].join("\n"));
done();
});
});
it("Should throw syntax error (%.a)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/dot_2.js';
exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/dot_2.js:1,0",
"%.a;",
"^",
"ERROR: Unexpected token: operator (%)"
].join("\n"));
done();
});
});
it("Should throw syntax error (a./();)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/dot_3.js';
exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/dot_3.js:1,2",
"a./();",
" ^",
"ERROR: Unexpected token: operator (/)"
].join("\n"));
done();
});
});
it("Should throw syntax error ({%: 1})", function(done) {
var command = uglifyjscmd + ' test/input/invalid/object.js';
exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/object.js:1,13",
"console.log({%: 1});",
" ^",
"ERROR: Unexpected token: operator (%)"
].join("\n"));
done();
});
});
it("Should throw syntax error (const a)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/const.js';
exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/const.js:7,11",
" const a;",
" ^",
"ERROR: Missing initializer in const declaration"
].join("\n"));
done();
});
});
it("Should throw syntax error (delete x)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/delete.js';
exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/delete.js:13,11",
" delete x;",
" ^",
"ERROR: Calling delete on expression not allowed in strict mode"
].join("\n"));
done();
});
});
it("Should throw syntax error (function g(arguments))", function(done) {
var command = uglifyjscmd + ' test/input/invalid/function_1.js';
exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/function_1.js:4,11",
"function g(arguments) {",
" ^",
"ERROR: Unexpected arguments in strict mode"
].join("\n"));
done();
});
});
it("Should throw syntax error (function eval())", function(done) {
var command = uglifyjscmd + ' test/input/invalid/function_2.js';
exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/function_2.js:4,9",
"function eval() {",
" ^",
"ERROR: Unexpected eval in strict mode"
].join("\n"));
done();
});
});
it("Should throw syntax error (iife arguments())", function(done) {
var command = uglifyjscmd + ' test/input/invalid/function_3.js';
exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/function_3.js:4,10",
"!function arguments() {",
" ^",
"ERROR: Unexpected arguments in strict mode"
].join("\n"));
done();
});
});
it("Should throw syntax error (catch(eval))", function(done) {
var command = uglifyjscmd + ' test/input/invalid/try.js';
exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/try.js:7,18",
" try {} catch (eval) {}",
" ^",
"ERROR: Unexpected eval in strict mode"
].join("\n"));
done();
});
});
it("Should throw syntax error (var eval)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/var.js';
exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/var.js:7,8",
" var eval;",
" ^",
"ERROR: Unexpected eval in strict mode"
].join("\n"));
done();
});
});
it("Should handle literal string as source map input", function(done) {
var command = [
uglifyjscmd,
"test/input/issue-1236/simple.js",
"--source-map",
'content="' + read_map() + '",url=inline'
].join(" ");
exec(command, function (err, stdout) {
if (err) throw err;
assert.strictEqual(stdout, [
'"use strict";var foo=function foo(x){return"foo "+x};console.log(foo("bar"));',
"//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImluZGV4LmpzIl0sIm5hbWVzIjpbImZvbyIsIngiLCJjb25zb2xlIiwibG9nIl0sIm1hcHBpbmdzIjoiWUFBQSxJQUFJQSxLQUFNLFFBQU5BLEtBQU1DLEdBQUEsTUFBSyxPQUFTQSxFQUN4QkMsU0FBUUMsSUFBSUgsSUFBSSJ9",
""
].join("\n"));
done();
});
function read_map() {
var map = JSON.parse(read("./test/input/issue-1236/simple.js.map"));
delete map.sourcesContent;
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() {
@@ -75,7 +75,6 @@ describe("comment filters", function() {
it("Should handle shebang and preamble correctly", function() { it("Should handle shebang and preamble correctly", function() {
var code = UglifyJS.minify("#!/usr/bin/node\nvar x = 10;", { var code = UglifyJS.minify("#!/usr/bin/node\nvar x = 10;", {
fromString: true,
output: { preamble: "/* Build */" } output: { preamble: "/* Build */" }
}).code; }).code;
assert.strictEqual(code, "#!/usr/bin/node\n/* Build */\nvar x=10;"); assert.strictEqual(code, "#!/usr/bin/node\n/* Build */\nvar x=10;");
@@ -83,7 +82,6 @@ describe("comment filters", function() {
it("Should handle preamble without shebang correctly", function() { it("Should handle preamble without shebang correctly", function() {
var code = UglifyJS.minify("var x = 10;", { var code = UglifyJS.minify("var x = 10;", {
fromString: true,
output: { preamble: "/* Build */" } output: { preamble: "/* Build */" }
}).code; }).code;
assert.strictEqual(code, "/* Build */\nvar x=10;"); assert.strictEqual(code, "/* Build */\nvar x=10;");

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() {
@@ -20,7 +20,7 @@ describe("Comment", function() {
for (var i = 0; i < tests.length; i++) { for (var i = 0; i < tests.length; i++) {
assert.throws(function() { assert.throws(function() {
uglify.parse(tests[i], {fromString: true}) uglify.parse(tests[i]);
}, fail, tests[i]); }, fail, tests[i]);
} }
}); });
@@ -43,7 +43,7 @@ describe("Comment", function() {
for (var i = 0; i < tests.length; i++) { for (var i = 0; i < tests.length; i++) {
assert.throws(function() { assert.throws(function() {
uglify.parse(tests[i], {fromString: true}) uglify.parse(tests[i]);
}, fail, tests[i]); }, fail, tests[i]);
} }
}); });

View File

@@ -6,9 +6,7 @@ describe("comment before constant", function() {
it("Should test comment before constant is retained and output after mangle.", function() { it("Should test comment before constant is retained and output after mangle.", function() {
var result = Uglify.minify(js, { var result = Uglify.minify(js, {
fromString: true,
compress: { collapse_vars: false, reduce_vars: false }, compress: { collapse_vars: false, reduce_vars: false },
mangle: {},
output: { comments: true }, output: { comments: true },
}); });
assert.strictEqual(result.code, 'function f(){/*c1*/var/*c2*/n=/*c3*/!1;return n}'); assert.strictEqual(result.code, 'function f(){/*c1*/var/*c2*/n=/*c3*/!1;return n}');
@@ -16,12 +14,9 @@ describe("comment before constant", function() {
it("Should test code works when comments disabled.", function() { it("Should test code works when comments disabled.", function() {
var result = Uglify.minify(js, { var result = Uglify.minify(js, {
fromString: true,
compress: { collapse_vars: false, reduce_vars: false }, compress: { collapse_vars: false, reduce_vars: false },
mangle: {},
output: { comments: false }, output: { comments: false },
}); });
assert.strictEqual(result.code, 'function f(){var n=!1;return n}'); assert.strictEqual(result.code, 'function f(){var n=!1;return n}');
}); });
}); });

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() {
@@ -197,7 +197,7 @@ describe("Directives", function() {
assert.strictEqual( assert.strictEqual(
uglify.minify( uglify.minify(
'"use strict";\'use strict\';"use strict";"use strict";;\'use strict\';console.log(\'use strict\');', '"use strict";\'use strict\';"use strict";"use strict";;\'use strict\';console.log(\'use strict\');',
{fromString: true, output: {beautify: true, quote_style: 3}, compress: false} {output: {beautify: true, quote_style: 3}, compress: false}
).code, ).code,
'"use strict";\n\n\'use strict\';\n\n"use strict";\n\n"use strict";\n\n;\'use strict\';\n\nconsole.log(\'use strict\');' '"use strict";\n\n\'use strict\';\n\n"use strict";\n\n"use strict";\n\n;\'use strict\';\n\nconsole.log(\'use strict\');'
); );
@@ -225,7 +225,7 @@ describe("Directives", function() {
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], {fromString: true, quote_style: 3, compress: false, mangle: false}).code, uglify.minify(tests[i][0], {compress: false, mangle: false}).code,
tests[i][1], tests[i][1],
tests[i][0] tests[i][0]
); );
@@ -234,7 +234,7 @@ describe("Directives", function() {
it("Should add double semicolon when relying on automatic semicolon insertion", function() { it("Should add double semicolon when relying on automatic semicolon insertion", function() {
var code = uglify.minify('"use strict";"use\\x20strict";', var code = uglify.minify('"use strict";"use\\x20strict";',
{fromString: true, output: {semicolons: false}, compress: false} {output: {semicolons: false}, compress: false}
).code; ).code;
assert.strictEqual(code, '"use strict";;"use strict"\n'); assert.strictEqual(code, '"use strict";;"use strict"\n');
}); });
@@ -340,7 +340,7 @@ describe("Directives", function() {
]; ];
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], {fromString: true, output:{quote_style: tests[i][1]}, compress: false}).code, uglify.minify(tests[i][0], {output:{quote_style: tests[i][1]}, compress: false}).code,
tests[i][2], tests[i][2],
tests[i][0] + " using mode " + tests[i][1] tests[i][0] + " using mode " + tests[i][1]
); );
@@ -362,7 +362,7 @@ describe("Directives", function() {
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], {fromString: true, compress: {collapse_vars: true, side_effects: true}}).code, uglify.minify(tests[i][0], {compress: {collapse_vars: true, side_effects: true}}).code,
tests[i][1], tests[i][1],
tests[i][0] tests[i][0]
); );

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,58 +1,80 @@
var Uglify = require('../../');
var assert = require("assert"); var assert = require("assert");
var exec = require("child_process").exec;
var path = require("path"); var path = require("path");
var readFileSync = require("fs").readFileSync;
describe("minify() with input file globs", function() { describe("bin/uglifyjs with input file globs", function() {
it("minify() with one input file glob string.", function() { var uglifyjscmd = '"' + process.argv[0] + '" bin/uglifyjs';
var result = Uglify.minify("test/input/issue-1242/foo.*"); it("bin/uglifyjs with one input file extension glob.", function(done) {
assert.strictEqual(result.code, 'function foo(o){print("Foo:",2*o)}var print=console.log.bind(console);'); var command = uglifyjscmd + ' "test/input/issue-1242/foo.*" -cm';
});
it("minify() with an array of one input file glob.", function() { exec(command, function(err, stdout) {
var result = Uglify.minify([ if (err) throw err;
"test/input/issue-1242/b*.es5",
]); assert.strictEqual(stdout, 'function foo(o){print("Foo:",2*o)}var print=console.log.bind(console);\n');
assert.strictEqual(result.code, 'function bar(n){return 3*n}function baz(n){return n/2}'); done();
});
it("minify() with an array of multiple input file globs.", function() {
var result = Uglify.minify([
"test/input/issue-1242/???.es5",
"test/input/issue-1242/*.js",
], {
compress: { toplevel: true }
}); });
assert.strictEqual(result.code, 'var print=console.log.bind(console),a=function(n){return 3*n}(3),b=function(n){return n/2}(12);print("qux",a,b),function(n){print("Foo:",2*n)}(11);');
}); });
it("should throw with non-matching glob string", function() { it("bin/uglifyjs with one input file name glob.", function(done) {
var glob = "test/input/issue-1242/blah.*"; var command = uglifyjscmd + ' "test/input/issue-1242/b*.es5" -cm';
assert.strictEqual(Uglify.simple_glob(glob).length, 1);
assert.strictEqual(Uglify.simple_glob(glob)[0], glob); exec(command, function(err, stdout) {
assert.throws(function() { if (err) throw err;
Uglify.minify(glob);
}, "should throw file not found"); assert.strictEqual(stdout, 'function bar(n){return 3*n}function baz(n){return n/2}\n');
done();
});
}); });
it('"?" in glob string should not match "/"', function() { it("bin/uglifyjs with multiple input file globs.", function(done) {
var glob = "test/input?issue-1242/foo.*"; var command = uglifyjscmd + ' "test/input/issue-1242/???.es5" "test/input/issue-1242/*.js" -mc toplevel';
assert.strictEqual(Uglify.simple_glob(glob).length, 1);
assert.strictEqual(Uglify.simple_glob(glob)[0], glob); exec(command, function(err, stdout) {
assert.throws(function() { if (err) throw err;
Uglify.minify(glob);
}, "should throw file not found"); assert.strictEqual(stdout, 'var print=console.log.bind(console),a=function(n){return 3*n}(3),b=function(n){return n/2}(12);print("qux",a,b),function(n){print("Foo:",2*n)}(11);\n');
done();
});
}); });
it("should handle special characters in glob string", function() { it("should throw with non-matching glob string", function(done) {
var result = Uglify.minify("test/input/issue-1632/^{*}[???](*)+$.??"); var command = uglifyjscmd + ' "test/input/issue-1242/blah.*"';
assert.strictEqual(result.code, "console.log(x);");
exec(command, function(err, stdout, stderr) {
assert.ok(err);
assert.ok(/^ERROR: ENOENT/.test(stderr));
done();
});
}); });
it("should handle array of glob strings - matching and otherwise", function() { it('"?" in glob string should not match "/"', function(done) {
var command = uglifyjscmd + ' "test/input?issue-1242/foo.*"';
exec(command, function(err, stdout, stderr) {
assert.ok(err);
assert.ok(/^ERROR: ENOENT/.test(stderr));
done();
});
});
it("should handle special characters in glob string", function(done) {
var command = uglifyjscmd + ' "test/input/issue-1632/^{*}[???](*)+$.??" -cm';
exec(command, function(err, stdout) {
if (err) throw err;
assert.strictEqual(stdout, "console.log(x);\n");
done();
});
});
it("should handle array of glob strings - matching and otherwise", function(done) {
var dir = "test/input/issue-1242"; var dir = "test/input/issue-1242";
var matches = Uglify.simple_glob([ var command = uglifyjscmd + ' "' + [
path.join(dir, "b*.es5"), path.join(dir, "b*.es5"),
path.join(dir, "z*.es5"), path.join(dir, "z*.es5"),
path.join(dir, "*.js"), path.join(dir, "*.js")
]); ].join('" "') + '"';
assert.strictEqual(matches.length, 4);
assert.strictEqual(matches[0], path.join(dir, "bar.es5")); exec(command, function(err, stdout, stderr) {
assert.strictEqual(matches[1], path.join(dir, "baz.es5")); assert.ok(err);
assert.strictEqual(matches[2], path.join(dir, "z*.es5")); assert.ok(/^ERROR: ENOENT.*?z\*\.es5/.test(stderr));
assert.strictEqual(matches[3], path.join(dir, "qux.js")); done();
});
}); });
}); });

View File

@@ -8,12 +8,7 @@ describe("Huge number of comments.", function() {
for (i = 1; i <= 5000; ++i) { js += "// " + i + "\n"; } for (i = 1; i <= 5000; ++i) { js += "// " + i + "\n"; }
for (; i <= 10000; ++i) { js += "/* " + i + " */ /**/"; } for (; i <= 10000; ++i) { js += "/* " + i + " */ /**/"; }
js += "x; }"; js += "x; }";
var result = Uglify.minify(js, { var result = Uglify.minify(js, { mangle: false });
fromString: true,
mangle: false,
compress: {}
});
assert.strictEqual(result.code, "function lots_of_comments(x){return 7-x}"); assert.strictEqual(result.code, "function lots_of_comments(x){return 7-x}");
}); });
}); });

View File

@@ -25,9 +25,9 @@ describe("input sourcemaps", function() {
transpilemap = sourceMap || getMap(); transpilemap = sourceMap || getMap();
var result = Uglify.minify(transpiled, { var result = Uglify.minify(transpiled, {
fromString: true, sourceMap: {
inSourceMap: transpilemap, content: transpilemap
outSourceMap: true }
}); });
map = new SourceMapConsumer(result.map); map = new SourceMapConsumer(result.map);

View File

@@ -11,7 +11,7 @@ describe("let", function() {
s += "var v" + i + "=0;"; s += "var v" + i + "=0;";
} }
s += '}'; s += '}';
var result = Uglify.minify(s, {fromString: true, compress: false}); var result = Uglify.minify(s, {compress: false});
// Verify that select keywords and reserved keywords not produced // Verify that select keywords and reserved keywords not produced
assert.strictEqual(result.code.indexOf("var let="), -1); assert.strictEqual(result.code.indexOf("var let="), -1);

View File

@@ -1,11 +1,10 @@
var Uglify = require('../../'); var Uglify = require("../node");
var assert = require("assert"); var assert = require("assert");
describe("line-endings", function() { describe("line-endings", function() {
var options = { var options = {
fromString: true,
mangle: false,
compress: false, compress: false,
mangle: false,
output: { output: {
beautify: false, beautify: false,
comments: /^!/, comments: /^!/,

View File

@@ -6,43 +6,41 @@ describe("Input file as map", function() {
var jsMap = { var jsMap = {
'/scripts/foo.js': 'var foo = {"x": 1, y: 2, \'z\': 3};' '/scripts/foo.js': 'var foo = {"x": 1, y: 2, \'z\': 3};'
}; };
var result = Uglify.minify(jsMap, {fromString: true, outSourceMap: true}); var result = Uglify.minify(jsMap, {sourceMap: true});
var map = JSON.parse(result.map); var map = JSON.parse(result.map);
assert.strictEqual(result.code, 'var foo={x:1,y:2,z:3};'); assert.strictEqual(result.code, 'var foo={x:1,y:2,z:3};');
assert.deepEqual(map.sources, ['/scripts/foo.js']); assert.deepEqual(map.sources, ['/scripts/foo.js']);
assert.strictEqual(map.file, undefined); assert.strictEqual(map.file, undefined);
result = Uglify.minify(jsMap, {fromString: true, outFileName: 'out.js'}); result = Uglify.minify(jsMap);
assert.strictEqual(result.map, null); assert.strictEqual(result.map, undefined);
result = Uglify.minify(jsMap, {fromString: true, outFileName: 'out.js', outSourceMap: true}); result = Uglify.minify(jsMap, {sourceMap: {filename: 'out.js'}});
map = JSON.parse(result.map); map = JSON.parse(result.map);
assert.strictEqual(map.file, 'out.js'); assert.strictEqual(map.file, 'out.js');
}); });
it("Should accept array of objects and strings", function() { it("Should accept array of strings", function() {
var jsSeq = [ var jsSeq = [
{'/scripts/foo.js': 'var foo = {"x": 1, y: 2, \'z\': 3};'}, 'var foo = {"x": 1, y: 2, \'z\': 3};',
'var bar = 15;' 'var bar = 15;'
]; ];
var result = Uglify.minify(jsSeq, {fromString: true, outSourceMap: true}); var result = Uglify.minify(jsSeq, {sourceMap: true});
var map = JSON.parse(result.map); var map = JSON.parse(result.map);
assert.strictEqual(result.code, 'var foo={x:1,y:2,z:3},bar=15;'); assert.strictEqual(result.code, 'var foo={x:1,y:2,z:3},bar=15;');
assert.strictEqual(map.sources[0], '/scripts/foo.js'); assert.deepEqual(map.sources, ['0', '1']);
}); });
it("Should correctly include source", function() { it("Should correctly include source", function() {
var jsSeq = [ var jsMap = {
{'/scripts/foo.js': 'var foo = {"x": 1, y: 2, \'z\': 3};'}, '/scripts/foo.js': 'var foo = {"x": 1, y: 2, \'z\': 3};'
'var bar = 15;' };
]; var result = Uglify.minify(jsMap, {sourceMap: {includeSources: true}});
var result = Uglify.minify(jsSeq, {fromString: true, outSourceMap: true, sourceMapIncludeSources: true});
var map = JSON.parse(result.map); var map = JSON.parse(result.map);
assert.strictEqual(result.code, 'var foo={x:1,y:2,z:3},bar=15;'); assert.strictEqual(result.code, 'var foo={x:1,y:2,z:3};');
assert.deepEqual(map.sourcesContent, ['var foo = {"x": 1, y: 2, \'z\': 3};', 'var bar = 15;']); assert.deepEqual(map.sourcesContent, ['var foo = {"x": 1, y: 2, \'z\': 3};']);
}); });
}); });

View File

@@ -2,10 +2,14 @@ var Uglify = require('../../');
var assert = require("assert"); var assert = require("assert");
var readFileSync = require("fs").readFileSync; var readFileSync = require("fs").readFileSync;
function read(path) {
return readFileSync(path, "utf8");
}
describe("minify", function() { describe("minify", function() {
it("Should test basic sanity of minify with default options", function() { it("Should test basic sanity of minify with default options", function() {
var js = 'function foo(bar) { if (bar) return 3; else return 7; var u = not_called(); }'; var js = 'function foo(bar) { if (bar) return 3; else return 7; var u = not_called(); }';
var result = Uglify.minify(js, {fromString: true}); var result = Uglify.minify(js);
assert.strictEqual(result.code, 'function foo(n){return n?3:7}'); assert.strictEqual(result.code, 'function foo(n){return n?3:7}');
}); });
@@ -13,7 +17,7 @@ describe("minify", function() {
it("Should preserve quotes in object literals", function() { it("Should preserve quotes in object literals", function() {
var js = 'var foo = {"x": 1, y: 2, \'z\': 3};'; var js = 'var foo = {"x": 1, y: 2, \'z\': 3};';
var result = Uglify.minify(js, { var result = Uglify.minify(js, {
fromString: true, output: { output: {
keep_quoted_props: true keep_quoted_props: true
}}); }});
assert.strictEqual(result.code, 'var foo={"x":1,y:2,"z":3};'); assert.strictEqual(result.code, 'var foo={"x":1,y:2,"z":3};');
@@ -22,7 +26,7 @@ describe("minify", function() {
it("Should preserve quote styles when quote_style is 3", function() { it("Should preserve quote styles when quote_style is 3", function() {
var js = 'var foo = {"x": 1, y: 2, \'z\': 3};'; var js = 'var foo = {"x": 1, y: 2, \'z\': 3};';
var result = Uglify.minify(js, { var result = Uglify.minify(js, {
fromString: true, output: { output: {
keep_quoted_props: true, keep_quoted_props: true,
quote_style: 3 quote_style: 3
}}); }});
@@ -32,7 +36,7 @@ describe("minify", function() {
it("Should not preserve quotes in object literals when disabled", function() { it("Should not preserve quotes in object literals when disabled", function() {
var js = 'var foo = {"x": 1, y: 2, \'z\': 3};'; var js = 'var foo = {"x": 1, y: 2, \'z\': 3};';
var result = Uglify.minify(js, { var result = Uglify.minify(js, {
fromString: true, output: { output: {
keep_quoted_props: false, keep_quoted_props: false,
quote_style: 3 quote_style: 3
}}); }});
@@ -44,12 +48,13 @@ describe("minify", function() {
it("Shouldn't mangle quoted properties", function() { it("Shouldn't mangle quoted properties", function() {
var js = 'a["foo"] = "bar"; a.color = "red"; x = {"bar": 10};'; var js = 'a["foo"] = "bar"; a.color = "red"; x = {"bar": 10};';
var result = Uglify.minify(js, { var result = Uglify.minify(js, {
fromString: true,
compress: { compress: {
properties: false properties: false
}, },
mangleProperties: { mangle: {
ignore_quoted: true properties: {
keep_quoted: true
}
}, },
output: { output: {
keep_quoted_props: true, keep_quoted_props: true,
@@ -63,10 +68,12 @@ describe("minify", function() {
describe("inSourceMap", function() { describe("inSourceMap", function() {
it("Should read the given string filename correctly when sourceMapIncludeSources is enabled (#1236)", function() { it("Should read the given string filename correctly when sourceMapIncludeSources is enabled (#1236)", function() {
var result = Uglify.minify('./test/input/issue-1236/simple.js', { var result = Uglify.minify(read("./test/input/issue-1236/simple.js"), {
outSourceMap: "simple.min.js.map", sourceMap: {
inSourceMap: "./test/input/issue-1236/simple.js.map", content: read("./test/input/issue-1236/simple.js.map"),
sourceMapIncludeSources: true filename: "simple.min.js",
includeSources: true
}
}); });
var map = JSON.parse(result.map); var map = JSON.parse(result.map);
@@ -77,10 +84,12 @@ describe("minify", function() {
'let foo = x => "foo " + x;\nconsole.log(foo("bar"));'); 'let foo = x => "foo " + x;\nconsole.log(foo("bar"));');
}); });
it("Should process inline source map", function() { it("Should process inline source map", function() {
var code = Uglify.minify("./test/input/issue-520/input.js", { var code = Uglify.minify(read("./test/input/issue-520/input.js"), {
compress: { toplevel: true }, compress: { toplevel: true },
inSourceMap: "inline", sourceMap: {
sourceMapInline: true content: "inline",
url: "inline"
}
}).code + "\n"; }).code + "\n";
assert.strictEqual(code, readFileSync("test/input/issue-520/output.js", "utf8")); assert.strictEqual(code, readFileSync("test/input/issue-520/output.js", "utf8"));
}); });
@@ -91,9 +100,11 @@ describe("minify", function() {
warnings.push(txt); warnings.push(txt);
}; };
try { try {
var result = Uglify.minify("./test/input/issue-1323/sample.js", { var result = Uglify.minify(read("./test/input/issue-1323/sample.js"), {
inSourceMap: "inline",
mangle: false, mangle: false,
sourceMap: {
content: "inline"
}
}); });
assert.strictEqual(result.code, "var bar=function(){function foo(bar){return bar}return foo}();"); assert.strictEqual(result.code, "var bar=function(){function foo(bar){return bar}return foo}();");
assert.strictEqual(warnings.length, 1); assert.strictEqual(warnings.length, 1);
@@ -103,41 +114,34 @@ 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"),
"./test/input/issue-520/input.js", read("./test/input/issue-520/output.js")
"./test/input/issue-520/output.js" ], {
], { sourceMap: {
inSourceMap: "inline", content: "inline",
sourceMapInline: true url: "inline"
}); }
});
});
it("Should fail with SpiderMonkey and inline source map", function() {
assert.throws(function() {
Uglify.minify("./test/input/issue-520/input.js", {
inSourceMap: "inline",
sourceMapInline: true,
spidermonkey: true
});
}); });
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");
}); });
}); });
describe("sourceMapInline", function() { describe("sourceMapInline", function() {
it("should append source map to output js when sourceMapInline is enabled", function() { it("should append source map to output js when sourceMapInline is enabled", function() {
var result = Uglify.minify('var a = function(foo) { return foo; };', { var result = Uglify.minify('var a = function(foo) { return foo; };', {
fromString: true, sourceMap: {
sourceMapInline: true url: "inline"
}
}); });
var code = result.code; var code = result.code;
assert.strictEqual(code, "var a=function(n){return n};\n" + assert.strictEqual(code, "var a=function(n){return n};\n" +
"//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIj8iXSwibmFtZXMiOlsiYSIsImZvbyJdLCJtYXBwaW5ncyI6IkFBQUEsR0FBSUEsR0FBSSxTQUFTQyxHQUFPLE1BQU9BIn0="); "//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjAiXSwibmFtZXMiOlsiYSIsImZvbyJdLCJtYXBwaW5ncyI6IkFBQUEsR0FBSUEsR0FBSSxTQUFTQyxHQUFPLE1BQU9BIn0=");
}); });
it("should not append source map to output js when sourceMapInline is not enabled", function() { it("should not append source map to output js when sourceMapInline is not enabled", function() {
var result = Uglify.minify('var a = function(foo) { return foo; };', { var result = Uglify.minify('var a = function(foo) { return foo; };');
fromString: true
});
var code = result.code; var code = result.code;
assert.strictEqual(code, "var a=function(n){return n};"); assert.strictEqual(code, "var a=function(n){return n};");
}); });
@@ -146,7 +150,6 @@ describe("minify", function() {
describe("#__PURE__", function() { describe("#__PURE__", function() {
it("should drop #__PURE__ hint after use", function() { it("should drop #__PURE__ hint after use", function() {
var result = Uglify.minify('//@__PURE__ comment1 #__PURE__ comment2\n foo(), bar();', { var result = Uglify.minify('//@__PURE__ comment1 #__PURE__ comment2\n foo(), bar();', {
fromString: true,
output: { output: {
comments: "all", comments: "all",
beautify: false, beautify: false,
@@ -157,7 +160,6 @@ describe("minify", function() {
}); });
it("should not drop #__PURE__ hint if function is retained", function() { it("should not drop #__PURE__ hint if function is retained", function() {
var result = Uglify.minify("var a = /*#__PURE__*/(function(){ foo(); })();", { var result = Uglify.minify("var a = /*#__PURE__*/(function(){ foo(); })();", {
fromString: true,
output: { output: {
comments: "all", comments: "all",
beautify: false, beautify: false,
@@ -169,27 +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{}", { fromString: true }); 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

@@ -34,7 +34,6 @@ describe("New", function() {
for (var i = 0; i < tests.length; i++) { for (var i = 0; i < tests.length; i++) {
assert.strictEqual( assert.strictEqual(
uglify.minify(tests[i], { uglify.minify(tests[i], {
fromString: true,
output: {beautify: true}, output: {beautify: true},
compress: false, compress: false,
mangle: false mangle: false
@@ -76,7 +75,6 @@ describe("New", function() {
for (var i = 0; i < tests.length; i++) { for (var i = 0; i < tests.length; i++) {
assert.strictEqual( assert.strictEqual(
uglify.minify(tests[i], { uglify.minify(tests[i], {
fromString: true,
output: {beautify: false}, output: {beautify: false},
compress: false, compress: false,
mangle: false mangle: false

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 UglifyJS = require("../../"); var UglifyJS = require("../node");
var assert = require("assert"); var assert = require("assert");
describe("operator", function() { describe("operator", function() {

View File

@@ -42,8 +42,8 @@ describe("test/jetstream.js", function() {
run("npm", ["install", "phantomjs-prebuilt@2.1.14"], done); run("npm", ["install", "phantomjs-prebuilt@2.1.14"], done);
}); });
[ [
"-mc warnings=false", "-mc",
"-mc keep_fargs=false,passes=3,pure_getters,unsafe,unsafe_comps,unsafe_math,unsafe_proto,warnings=false", "-mc keep_fargs=false,passes=3,pure_getters,unsafe,unsafe_comps,unsafe_math,unsafe_proto",
].forEach(function(options) { ].forEach(function(options) {
it("Should pass with options " + options, function(done) { it("Should pass with options " + options, function(done) {
var args = options.split(/ /); var args = options.split(/ /);

View File

@@ -13,9 +13,7 @@ describe("screw-ie8", function () {
}\ }\
console.log('undefined is ' + undefined);\ console.log('undefined is ' + undefined);\
return b === undefined;\ return b === undefined;\
};", { };"
fromString: true
}
).code, ).code,
'function a(o){try{throw"Stuff"}catch(o){console.log("caught: "+o)}return console.log("undefined is "+void 0),void 0===o}' 'function a(o){try{throw"Stuff"}catch(o){console.log("caught: "+o)}return console.log("undefined is "+void 0),void 0===o}'
); );

View File

@@ -1,32 +1,21 @@
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(20000);
var uglifyjs = '"' + process.argv[0] + '" bin/uglifyjs'; var uglifyjs = '"' + process.argv[0] + '" bin/uglifyjs';
var command = uglifyjs + " --self -cm --wrap SpiderUglify --dump-spidermonkey-ast | " + var command = uglifyjs + " --self -cm --wrap SpiderUglify -o spidermonkey | " +
uglifyjs + " --spidermonkey -cm"; uglifyjs + " -p spidermonkey -cm";
exec(command, function (err, stdout) { exec(command, function(err, stdout) {
if (err) throw err; if (err) throw err;
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("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,7 +1,7 @@
// Testing UglifyJS <-> SpiderMonkey AST conversion // Testing UglifyJS <-> SpiderMonkey AST conversion
// through generative testing. // through generative testing.
var UglifyJS = require(".."), var UglifyJS = require("./node"),
escodegen = require("escodegen"), escodegen = require("escodegen"),
esfuzz = require("esfuzz"), esfuzz = require("esfuzz"),
estraverse = require("estraverse"), estraverse = require("estraverse"),

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");

View File

@@ -1,18 +1,37 @@
var vm = require("vm"); var vm = require("vm");
function safe_log(arg) {
if (arg) switch (typeof arg) {
case "function":
return arg.toString();
case "object":
if (/Error$/.test(arg.name)) return arg.toString();
arg.constructor.toString();
for (var key in arg) {
arg[key] = safe_log(arg[key]);
}
}
return arg;
}
var FUNC_TOSTRING = [ var FUNC_TOSTRING = [
"Function.prototype.toString = Function.prototype.valueOf = function() {", "Function.prototype.toString = Function.prototype.valueOf = function() {",
" var ids = [];", " var id = 0;",
" return function() {", " return function() {",
" var i = ids.indexOf(this);", ' if (this === Array) return "[Function: Array]";',
" if (i < 0) {", ' if (this === Object) return "[Function: Object]";',
" i = ids.length;", " var i = this.name;",
" ids.push(this);", ' if (typeof i != "number") {',
" i = ++id;",
' Object.defineProperty(this, "name", {',
" get: function() {",
" return i;",
" }",
" });",
" }", " }",
' return "[Function: __func_" + i + "__]";', ' return "[Function: " + i + "]";',
" }", " }",
"}();", "}();",
""
].join("\n"); ].join("\n");
exports.run_code = function(code) { exports.run_code = function(code) {
var stdout = ""; var stdout = "";
@@ -21,15 +40,18 @@ exports.run_code = function(code) {
stdout += chunk; stdout += chunk;
}; };
try { try {
new vm.Script(FUNC_TOSTRING + code).runInNewContext({ vm.runInNewContext([
FUNC_TOSTRING,
"!function() {",
code,
"}();",
].join("\n"), {
console: { console: {
log: function() { log: function() {
return console.log.apply(console, [].map.call(arguments, function(arg) { return console.log.apply(console, [].map.call(arguments, safe_log));
return typeof arg == "function" ? arg.toString() : arg;
}));
} }
} }
}, { timeout: 30000 }); }, { timeout: 5000 });
return stdout; return stdout;
} catch (ex) { } catch (ex) {
return ex; return ex;

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

@@ -2,7 +2,7 @@
// derived from https://github.com/qfox/uglyfuzzer by Peter van der Zee // derived from https://github.com/qfox/uglyfuzzer by Peter van der Zee
"use strict"; "use strict";
// check both cli and file modes of nodejs (!). See #1695 for details. and the various settings of uglify. // check both CLI and file modes of nodejs (!). See #1695 for details. and the various settings of uglify.
// bin/uglifyjs s.js -c && bin/uglifyjs s.js -c passes=3 && bin/uglifyjs s.js -c passes=3 -m // bin/uglifyjs s.js -c && bin/uglifyjs s.js -c passes=3 && bin/uglifyjs s.js -c passes=3 -m
// cat s.js | node && node s.js && bin/uglifyjs s.js -c | node && bin/uglifyjs s.js -c passes=3 | node && bin/uglifyjs s.js -c passes=3 -m | node // cat s.js | node && node s.js && bin/uglifyjs s.js -c | node && bin/uglifyjs s.js -c passes=3 | node && bin/uglifyjs s.js -c passes=3 -m | node
@@ -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");
@@ -20,49 +20,26 @@ var MAX_GENERATED_TOPLEVELS_PER_RUN = 1;
var MAX_GENERATION_RECURSION_DEPTH = 12; var MAX_GENERATION_RECURSION_DEPTH = 12;
var INTERVAL_COUNT = 100; var INTERVAL_COUNT = 100;
var STMT_BLOCK = 0; var STMT_ARG_TO_ID = Object.create(null);
var STMT_IF_ELSE = 1; var STMTS_TO_USE = [];
var STMT_DO_WHILE = 2; function STMT_(name) {
var STMT_WHILE = 3; return STMT_ARG_TO_ID[name] = STMTS_TO_USE.push(STMTS_TO_USE.length) - 1;
var STMT_FOR_LOOP = 4; }
var STMT_SEMI = 5;
var STMT_EXPR = 6; var STMT_BLOCK = STMT_("block");
var STMT_SWITCH = 7; var STMT_IF_ELSE = STMT_("ifelse");
var STMT_VAR = 8; var STMT_DO_WHILE = STMT_("dowhile");
var STMT_RETURN_ETC = 9; var STMT_WHILE = STMT_("while");
var STMT_FUNC_EXPR = 10; var STMT_FOR_LOOP = STMT_("forloop");
var STMT_TRY = 11; var STMT_FOR_IN = STMT_("forin");
var STMT_C = 12; var STMT_SEMI = STMT_("semi");
var STMTS_TO_USE = [ var STMT_EXPR = STMT_("expr");
STMT_BLOCK, var STMT_SWITCH = STMT_("switch");
STMT_IF_ELSE, var STMT_VAR = STMT_("var");
STMT_DO_WHILE, var STMT_RETURN_ETC = STMT_("stop");
STMT_WHILE, var STMT_FUNC_EXPR = STMT_("funcexpr");
STMT_FOR_LOOP, var STMT_TRY = STMT_("try");
STMT_SEMI, var STMT_C = STMT_("c");
STMT_EXPR,
STMT_SWITCH,
STMT_VAR,
STMT_RETURN_ETC,
STMT_FUNC_EXPR,
STMT_TRY,
STMT_C,
];
var STMT_ARG_TO_ID = {
block: STMT_BLOCK,
ifelse: STMT_IF_ELSE,
dowhile: STMT_DO_WHILE,
while: STMT_WHILE,
forloop: STMT_FOR_LOOP,
semi: STMT_SEMI,
expr: STMT_EXPR,
switch: STMT_SWITCH,
var: STMT_VAR,
stop: STMT_RETURN_ETC,
funcexpr: STMT_FUNC_EXPR,
try: STMT_TRY,
c: STMT_C,
};
var STMT_FIRST_LEVEL_OVERRIDE = -1; var STMT_FIRST_LEVEL_OVERRIDE = -1;
var STMT_SECOND_LEVEL_OVERRIDE = -1; var STMT_SECOND_LEVEL_OVERRIDE = -1;
@@ -71,6 +48,8 @@ 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;
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':
@@ -79,6 +58,9 @@ 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');
@@ -97,6 +79,9 @@ 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 '--use-strict':
use_strict = true;
break;
case '--stmt-depth-from-func': case '--stmt-depth-from-func':
STMT_COUNT_FROM_GLOBAL = false; STMT_COUNT_FROM_GLOBAL = false;
break; break;
@@ -118,10 +103,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('--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');
console.log('--without-stmt <statement names>: a comma delimited black list of statements never to generate'); console.log('--without-stmt <statement names>: a comma delimited black list of statements never to generate');
@@ -135,6 +122,7 @@ for (var i = 2; i < process.argv.length; ++i) {
} }
var VALUES = [ var VALUES = [
'""',
'true', 'true',
'false', 'false',
' /[a2][^e]+$/ ', ' /[a2][^e]+$/ ',
@@ -221,15 +209,19 @@ var ASSIGNMENTS = [
'>>>=', '>>>=',
'%=' ]; '%=' ];
var UNARY_OPS = [ var UNARY_SAFE = [
'--', '+',
'++', '-',
'~', '~',
'!', '!',
'void ', 'void ',
'delete ', // should be safe, even `delete foo` and `delete f()` shouldn't crash 'delete ',
' - ', ];
' + ' ]; var UNARY_POSTFIX = [
'++',
'--',
];
var UNARY_PREFIX = UNARY_POSTFIX.concat(UNARY_SAFE);
var NO_COMMA = true; var NO_COMMA = true;
var COMMA_OK = false; var COMMA_OK = false;
@@ -250,26 +242,26 @@ var NO_DECL = true;
var DONT_STORE = true; var DONT_STORE = true;
var VAR_NAMES = [ var VAR_NAMES = [
'foo', 'a',
'bar', 'a',
'a',
'a', 'a',
'b', 'b',
'b',
'b',
'b',
'c', // prevent redeclaring this, avoid assigning to this 'c', // prevent redeclaring this, avoid assigning to this
'undefined', // fun! 'foo',
'eval', // mmmm, ok, also fun! 'foo',
'NaN', // mmmm, ok, also fun! 'bar',
'Infinity', // the fun never ends! 'bar',
'arguments', // this one is just creepy 'undefined',
'Math', // since Math is assumed to be a non-constructor/function it may trip certain cases 'NaN',
'Infinity',
'arguments',
'Math',
'parseInt', 'parseInt',
'parseFloat', ];
'isNaN',
'isFinite',
'decodeURI',
'decodeURIComponent',
'encodeURI',
'encodeURIComponent',
'Object'];
var INITIAL_NAMES_LEN = VAR_NAMES.length; var INITIAL_NAMES_LEN = VAR_NAMES.length;
var TYPEOF_OUTCOMES = [ var TYPEOF_OUTCOMES = [
@@ -286,15 +278,26 @@ var TYPEOF_OUTCOMES = [
var loops = 0; var loops = 0;
var funcs = 0; var funcs = 0;
var labels = 10000;
function rng(max) { function rng(max) {
var r = randomBytes(2).readUInt16LE(0) / 65536; var r = randomBytes(2).readUInt16LE(0) / 65536;
return Math.floor(max * r); return Math.floor(max * r);
} }
function strictMode() {
return use_strict && rng(4) == 0 ? '"use strict";' : '';
}
function createTopLevelCode() { function createTopLevelCode() {
if (rng(2) === 0) return createStatements(3, MAX_GENERATION_RECURSION_DEPTH, CANNOT_THROW, CANNOT_BREAK, CANNOT_CONTINUE, CANNOT_RETURN, 0); return [
return createFunctions(rng(MAX_GENERATED_TOPLEVELS_PER_RUN) + 1, MAX_GENERATION_RECURSION_DEPTH, IN_GLOBAL, ANY_TYPE, CANNOT_THROW, 0); strictMode(),
'var a = 100, b = 10, c = 0;',
rng(2) == 0
? createStatements(3, MAX_GENERATION_RECURSION_DEPTH, CANNOT_THROW, CANNOT_BREAK, CANNOT_CONTINUE, CANNOT_RETURN, 0)
: createFunctions(rng(MAX_GENERATED_TOPLEVELS_PER_RUN) + 1, MAX_GENERATION_RECURSION_DEPTH, IN_GLOBAL, ANY_TYPE, CANNOT_THROW, 0),
'console.log(null, a, b, c);' // preceding `null` makes for a cleaner output (empty string still shows up etc)
].join('\n');
} }
function createFunctions(n, recurmax, inGlobal, noDecl, canThrow, stmtDepth) { function createFunctions(n, recurmax, inGlobal, noDecl, canThrow, stmtDepth) {
@@ -306,6 +309,22 @@ function createFunctions(n, recurmax, inGlobal, noDecl, canThrow, stmtDepth) {
return s; return s;
} }
function createParams() {
var params = [];
for (var n = rng(4); --n >= 0;) {
params.push(createVarName(MANDATORY));
}
return params.join(', ');
}
function createArgs() {
var args = [];
for (var n = rng(4); --n >= 0;) {
args.push(createValue());
}
return args.join(', ');
}
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;
@@ -316,17 +335,29 @@ function createFunction(recurmax, inGlobal, noDecl, canThrow, stmtDepth) {
var s = ''; var s = '';
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 = 'function ' + name + '(' + createVarName(MANDATORY) + '){' + createFunctions(rng(5) + 1, Math.ceil(recurmax * 0.7), NOT_GLOBAL, ANY_TYPE, canThrow, stmtDepth) + '}\n'; s = [
'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 = 'function ' + name + '(' + createVarName(MANDATORY) + '){' + createStatements(3, recurmax, canThrow, CANNOT_THROW, CANNOT_CONTINUE, CAN_RETURN, stmtDepth) + '}\n'; s = [
'function ' + name + '(' + createParams() + '){',
strictMode(),
createStatements(3, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
'}',
''
].join('\n');
} }
VAR_NAMES.length = namesLenBefore; VAR_NAMES.length = namesLenBefore;
if (noDecl) s = '!' + s + '(' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ')'; if (noDecl) s = 'var ' + createVarName(MANDATORY) + ' = ' + s + '(' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ');';
// avoid "function statements" (decl inside statements) // avoid "function statements" (decl inside statements)
else if (inGlobal || rng(10) > 0) s += name + '();' else if (inGlobal || rng(10) > 0) s += 'var ' + createVarName(MANDATORY) + ' = ' + name + '(' + createArgs() + ');';
return s; return s;
@@ -341,6 +372,40 @@ function createStatements(n, recurmax, canThrow, canBreak, canContinue, cannotRe
return s; return s;
} }
function enableLoopControl(flag, defaultValue) {
return Array.isArray(flag) && flag.indexOf("") < 0 ? flag.concat("") : flag || defaultValue;
}
function createLabel(canBreak, canContinue) {
var label;
if (rng(10) < 3) {
label = ++labels;
if (Array.isArray(canBreak)) {
canBreak = canBreak.slice();
} else {
canBreak = canBreak ? [ "" ] : [];
}
canBreak.push(label);
if (Array.isArray(canContinue)) {
canContinue = canContinue.slice();
} else {
canContinue = canContinue ? [ "" ] : [];
}
canContinue.push(label);
}
return {
break: canBreak,
continue: canContinue,
target: label ? "L" + label + ": " : ""
};
}
function getLabel(label) {
if (!Array.isArray(label)) return "";
label = label[rng(label.length)];
return label && " L" + label;
}
function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) { function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) {
++stmtDepth; ++stmtDepth;
var loop = ++loops; var loop = ++loops;
@@ -356,17 +421,36 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
switch (target) { switch (target) {
case STMT_BLOCK: case STMT_BLOCK:
return '{' + createStatements(rng(5) + 1, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + '}'; var label = createLabel(canBreak);
return label.target + '{' + createStatements(rng(5) + 1, recurmax, canThrow, label.break, canContinue, cannotReturn, stmtDepth) + '}';
case STMT_IF_ELSE: case STMT_IF_ELSE:
return 'if (' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ')' + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + (rng(2) === 1 ? ' else ' + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) : ''); return 'if (' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ')' + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + (rng(2) === 1 ? ' else ' + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) : '');
case STMT_DO_WHILE: case STMT_DO_WHILE:
return '{var brake' + loop + ' = 5; do {' + createStatement(recurmax, canThrow, CAN_BREAK, CAN_CONTINUE, cannotReturn, stmtDepth) + '} while ((' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ') && --brake' + loop + ' > 0);}'; var label = createLabel(canBreak, canContinue);
canBreak = label.break || enableLoopControl(canBreak, CAN_BREAK);
canContinue = label.continue || enableLoopControl(canContinue, CAN_CONTINUE);
return '{var brake' + loop + ' = 5; ' + label.target + 'do {' + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + '} while ((' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ') && --brake' + loop + ' > 0);}';
case STMT_WHILE: case STMT_WHILE:
return '{var brake' + loop + ' = 5; while ((' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ') && --brake' + loop + ' > 0)' + createStatement(recurmax, canThrow, CAN_BREAK, CAN_CONTINUE, cannotReturn, stmtDepth) + '}'; var label = createLabel(canBreak, canContinue);
canBreak = label.break || enableLoopControl(canBreak, CAN_BREAK);
canContinue = label.continue || enableLoopControl(canContinue, CAN_CONTINUE);
return '{var brake' + loop + ' = 5; ' + label.target + 'while ((' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ') && --brake' + loop + ' > 0)' + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + '}';
case STMT_FOR_LOOP: case STMT_FOR_LOOP:
return 'for (var brake' + loop + ' = 5; (' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ') && brake' + loop + ' > 0; --brake' + loop + ')' + createStatement(recurmax, canThrow, CAN_BREAK, CAN_CONTINUE, cannotReturn, stmtDepth); var label = createLabel(canBreak, canContinue);
canBreak = label.break || enableLoopControl(canBreak, CAN_BREAK);
canContinue = label.continue || enableLoopControl(canContinue, CAN_CONTINUE);
return label.target + 'for (var brake' + loop + ' = 5; (' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ') && brake' + loop + ' > 0; --brake' + loop + ')' + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth);
case STMT_FOR_IN:
var label = createLabel(canBreak, canContinue);
canBreak = label.break || enableLoopControl(canBreak, CAN_BREAK);
canContinue = label.continue || enableLoopControl(canContinue, CAN_CONTINUE);
var optElementVar = '';
if (rng(5) > 1) {
optElementVar = 'c = 1 + c; var ' + createVarName(MANDATORY) + ' = expr' + loop + '[key' + loop + ']; ';
}
return '{var expr' + loop + ' = ' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + '; ' + label.target + ' for (var key' + loop + ' in expr' + loop + ') {' + optElementVar + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + '}}';
case STMT_SEMI: case STMT_SEMI:
return ';'; return use_strict && rng(20) === 0 ? '"use strict";' : ';';
case STMT_EXPR: case STMT_EXPR:
return createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ';'; return createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ';';
case STMT_SWITCH: case STMT_SWITCH:
@@ -393,23 +477,27 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
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:
switch (rng(3)) { switch (rng(8)) {
case 0:
case 1: case 1:
if (canBreak && rng(5) === 0) return 'break;';
if (canContinue && rng(5) === 0) return 'continue;';
if (cannotReturn) return createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
return '/*3*/return;';
case 2: case 2:
// must wrap in curlies to prevent orphaned `else` statement case 3:
if (canThrow && rng(5) === 0) return '{ throw ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + '}'; if (canBreak && rng(5) === 0) return 'break' + getLabel(canBreak) + ';';
if (canContinue && rng(5) === 0) return 'continue' + getLabel(canContinue) + ';';
if (cannotReturn) return createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';'; if (cannotReturn) return createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
return '{ /*1*/ return ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + '}'; if (rng(3) == 0) return '/*3*/return;';
default: return 'return ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
case 4:
// this is actually more like a parser test, but perhaps it hits some dead code elimination traps // this is actually more like a parser test, but perhaps it hits some dead code elimination traps
// must wrap in curlies to prevent orphaned `else` statement // must wrap in curlies to prevent orphaned `else` statement
// note: you can't `throw` without an expression so don't put a `throw` option in this case // note: you can't `throw` without an expression so don't put a `throw` option in this case
if (cannotReturn) return createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';'; if (cannotReturn) return createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
return '{ /*2*/ return\n' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + '}'; return '{ /*2*/ return\n' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + '}';
default:
// must wrap in curlies to prevent orphaned `else` statement
if (canThrow && rng(5) === 0) return '{ throw ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + '}';
if (cannotReturn) return createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
return '{ return ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + '}';
} }
case STMT_FUNC_EXPR: case STMT_FUNC_EXPR:
// "In non-strict mode code, functions can only be declared at top level, inside a block, or ..." // "In non-strict mode code, functions can only be declared at top level, inside a block, or ..."
@@ -440,90 +528,130 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
function createSwitchParts(recurmax, n, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) { function createSwitchParts(recurmax, n, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) {
var hadDefault = false; var hadDefault = false;
var s = ''; var s = [''];
canBreak = enableLoopControl(canBreak, CAN_BREAK);
while (n-- > 0) { while (n-- > 0) {
//hadDefault = n > 0; // disables weird `default` clause positioning (use when handling destabilizes) //hadDefault = n > 0; // disables weird `default` clause positioning (use when handling destabilizes)
if (hadDefault || rng(5) > 0) { if (hadDefault || rng(5) > 0) {
s += '' + s.push(
'case ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ':\n' + 'case ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ':',
createStatements(rng(3) + 1, recurmax, canThrow, CAN_BREAK, canContinue, cannotReturn, stmtDepth) + createStatements(rng(3) + 1, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth),
'\n' + rng(10) > 0 ? ' break;' : '/* fall-through */',
(rng(10) > 0 ? ' break;' : '/* fall-through */') + ''
'\n'; );
} else { } else {
hadDefault = true; hadDefault = true;
s += '' + s.push(
'default:\n' + 'default:',
createStatements(rng(3) + 1, recurmax, canThrow, CAN_BREAK, canContinue, cannotReturn, stmtDepth) + createStatements(rng(3) + 1, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth),
'\n'; ''
);
} }
} }
return s; return s.join('\n');
} }
function createExpression(recurmax, noComma, stmtDepth, canThrow) { function createExpression(recurmax, noComma, stmtDepth, canThrow) {
if (--recurmax < 0) { if (--recurmax < 0) {
return '(c = 1 + c, ' + createNestedBinaryExpr(recurmax, noComma) + ')'; // note: should return a simple non-recursing expression value! return '(c = 1 + c, ' + createNestedBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')'; // note: should return a simple non-recursing expression value!
} }
// since `a` and `b` are our canaries we want them more frequently than other expressions (1/3rd chance of a canary) // since `a` and `b` are our canaries we want them more frequently than other expressions (1/3rd chance of a canary)
var r = rng(6); switch (rng(6)) {
if (r < 1) return 'a++ + ' + _createExpression(recurmax, noComma, stmtDepth, canThrow); case 0:
if (r < 2) return '(--b) + ' + _createExpression(recurmax, noComma, stmtDepth, canThrow); return '(a++ + (' + _createExpression(recurmax, noComma, stmtDepth, canThrow) + '))';
if (r < 3) return '(c = c + 1) + ' + _createExpression(recurmax, noComma, stmtDepth, canThrow); // c only gets incremented case 1:
return '((--b) + (' + _createExpression(recurmax, noComma, stmtDepth, canThrow) + '))';
return _createExpression(recurmax, noComma, stmtDepth, canThrow); case 2:
return '((c = c + 1) + (' + _createExpression(recurmax, noComma, stmtDepth, canThrow) + '))'; // c only gets incremented
default:
return '(' + _createExpression(recurmax, noComma, stmtDepth, canThrow) + ')';
}
} }
function _createExpression(recurmax, noComma, stmtDepth, canThrow) { function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
switch (rng(15)) { var p = 0;
case 0: switch (rng(_createExpression.N)) {
return createUnaryOp() + (rng(2) === 1 ? 'a' : 'b'); case p++:
case 1: case p++:
return 'a' + (rng(2) == 1 ? '++' : '--'); return createUnaryPrefix() + (rng(2) === 1 ? 'a' : 'b');
case 2: case p++:
case p++:
return (rng(2) === 1 ? 'a' : 'b') + createUnaryPostfix();
case p++:
case p++:
// parens needed because assignments aren't valid unless they're the left-most op(s) in an expression // parens needed because assignments aren't valid unless they're the left-most op(s) in an expression
return '(b ' + createAssignment() + ' a)'; return 'b ' + createAssignment() + ' a';
case 3: case p++:
case p++:
return rng(2) + ' === 1 ? a : b'; return rng(2) + ' === 1 ? a : b';
case 4: case p++:
return createNestedBinaryExpr(recurmax, noComma) + createBinaryOp(noComma) + createExpression(recurmax, noComma, stmtDepth, canThrow); case p++:
case 5:
return createValue(); return createValue();
case 6: case p++:
return '(' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ')'; return createExpression(recurmax, COMMA_OK, stmtDepth, canThrow);
case 7: case p++:
return createExpression(recurmax, noComma, stmtDepth, canThrow) + '?' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ':' + createExpression(recurmax, noComma, stmtDepth, canThrow); return createExpression(recurmax, noComma, stmtDepth, canThrow) + '?' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ':' + createExpression(recurmax, noComma, stmtDepth, canThrow);
case 8: case p++:
case p++:
var nameLenBefore = VAR_NAMES.length; var nameLenBefore = VAR_NAMES.length;
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'; if (name == 'c') name = 'a';
var s = ''; var s = [];
switch(rng(4)) { switch (rng(5)) {
case 0: case 0:
s = '(function ' + name + '(){' + createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth) + '})()'; s.push(
'(function ' + name + '(){',
strictMode(),
createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
'})()'
);
break; break;
case 1: case 1:
s = '+function ' + name + '(){' + createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth) + '}()'; s.push(
'+function ' + name + '(){',
strictMode(),
createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
'}()'
);
break; break;
case 2: case 2:
s = '!function ' + name + '(){' + createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth) + '}()'; s.push(
'!function ' + name + '(){',
strictMode(),
createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
'}()'
);
break;
case 3:
s.push(
'void function ' + name + '(){',
strictMode(),
createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
'}()'
);
break; break;
default: default:
s = 'void function ' + name + '(){' + createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth) + '}()'; var instantiate = rng(4) ? 'new ' : '';
s.push(
instantiate + 'function ' + name + '(){',
strictMode()
);
if (instantiate) for (var i = rng(4); --i >= 0;) {
if (rng(2)) s.push('this.' + getDotKey() + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ';');
else s.push('this[' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']' + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ';');
}
s.push(
createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
'}'
);
break; break;
} }
VAR_NAMES.length = nameLenBefore; VAR_NAMES.length = nameLenBefore;
return s; return s.join('\n');
case 9: case p++:
case p++:
return createTypeofExpr(recurmax, stmtDepth, canThrow); return createTypeofExpr(recurmax, stmtDepth, canThrow);
case 10: case p++:
// you could statically infer that this is just `Math`, regardless of the other expression case p++:
// I don't think Uglify does this at this time...
return ''+
'new function(){ \n' +
(rng(2) === 1 ? createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + '\n' : '') +
'return Math;\n' +
'}';
case 11:
// more like a parser test but perhaps comment nodes mess up the analysis? // more like a parser test but perhaps comment nodes mess up the analysis?
// note: parens not needed for post-fix (since that's the default when ambiguous) // note: parens not needed for post-fix (since that's the default when ambiguous)
// for prefix ops we need parens to prevent accidental syntax errors. // for prefix ops we need parens to prevent accidental syntax errors.
@@ -533,58 +661,152 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
case 1: case 1:
return 'b/* ignore */--'; return 'b/* ignore */--';
case 2: case 2:
return '(++/* ignore */a)'; return '++/* ignore */a';
case 3: case 3:
return '(--/* ignore */b)'; return '--/* ignore */b';
case 4: case 4:
// only groups that wrap a single variable return a "Reference", so this is still valid. // only groups that wrap a single variable return a "Reference", so this is still valid.
// may just be a parser edge case that is invisible to uglify... // may just be a parser edge case that is invisible to uglify...
return '(--(b))'; return '--(b)';
case 5: case 5:
// classic 0.3-0.1 case; 1-0.1-0.1-0.1 is not 0.7 :) // classic 0.3-0.1 case; 1-0.1-0.1-0.1 is not 0.7 :)
return 'b + 1-0.1-0.1-0.1'; return 'b + 1-0.1-0.1-0.1';
default: default:
return '(--/* ignore */b)'; return '--/* ignore */b';
} }
case 12: case p++:
return createNestedBinaryExpr(recurmax, noComma); case p++:
case 13: return createNestedBinaryExpr(recurmax, noComma, stmtDepth, canThrow);
case p++:
case p++:
return createUnarySafePrefix() + '(' + createNestedBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')';
case p++:
return " ((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") || a || 3).toString() "; return " ((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") || a || 3).toString() ";
case 14: case p++:
return " /[abc4]/.test(((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") || b || 5).toString()) "; return " /[abc4]/.test(((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") || b || 5).toString()) ";
case p++:
return " ((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) +
") || " + rng(10) + ").toString()[" +
createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "] ";
case p++:
return createArrayLiteral(recurmax, COMMA_OK, stmtDepth, canThrow);
case p++:
return createObjectLiteral(recurmax, COMMA_OK, stmtDepth, canThrow);
case p++:
return createArrayLiteral(recurmax, COMMA_OK, stmtDepth, canThrow) + '[' +
createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']';
case p++:
return createObjectLiteral(recurmax, COMMA_OK, stmtDepth, canThrow) + '[' +
createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']';
case p++:
return createArrayLiteral(recurmax, COMMA_OK, stmtDepth, canThrow) + '.' + getDotKey();
case p++:
return createObjectLiteral(recurmax, COMMA_OK, stmtDepth, canThrow) + '.' + getDotKey();
case p++:
var name = getVarName();
return name + ' && ' + name + '[' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']';
case p++:
var name = getVarName();
return name + ' && ' + name + '.' + getDotKey();
} }
_createExpression.N = p;
return _createExpression(recurmax, noComma, stmtDepth, canThrow);
} }
function createNestedBinaryExpr(recurmax, noComma) { function createArrayLiteral(recurmax, noComma, stmtDepth, canThrow) {
recurmax = 3; // note that this generates 2^recurmax expression parts... make sure to cap it recurmax--;
return _createSimpleBinaryExpr(recurmax, noComma); var arr = "[";
for (var i = rng(6); --i >= 0;) {
// in rare cases produce an array hole element
var element = rng(20) ? createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) : "";
arr += element + ", ";
}
return arr + "]";
} }
function _createSimpleBinaryExpr(recurmax, noComma) {
var SAFE_KEYS = [
"length",
"foo",
"a",
"b",
"c",
"undefined",
"null",
"NaN",
"Infinity",
"in",
"var",
];
var KEYS = [
"''",
'"\t"',
'"-2"',
"0",
"1.5",
"3",
].concat(SAFE_KEYS);
function getDotKey() {
return SAFE_KEYS[rng(SAFE_KEYS.length)];
}
function createObjectLiteral(recurmax, noComma, stmtDepth, canThrow) {
recurmax--;
var obj = "({";
for (var i = rng(6); --i >= 0;) {
var key = KEYS[rng(KEYS.length)];
obj += key + ":(" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "), ";
}
return obj + "})";
}
function createNestedBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
recurmax = 3; // note that this generates 2^recurmax expression parts... make sure to cap it
return _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow);
}
function _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
return '(' + _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow)
+ createBinaryOp(noComma) + _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')';
}
function _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
// intentionally generate more hardcore ops // intentionally generate more hardcore ops
if (--recurmax < 0) return createValue(); if (--recurmax < 0) return createValue();
var r = rng(30); var assignee, expr;
if (r === 0) return '(c = c + 1, ' + _createSimpleBinaryExpr(recurmax, noComma) + ')'; switch (rng(30)) {
var s = _createSimpleBinaryExpr(recurmax, noComma) + createBinaryOp(noComma) + _createSimpleBinaryExpr(recurmax, noComma); case 0:
if (r === 1) { return '(c = c + 1, ' + _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')';
// try to get a generated name reachable from current scope. default to just `a` case 1:
var assignee = VAR_NAMES[INITIAL_NAMES_LEN + rng(VAR_NAMES.length - INITIAL_NAMES_LEN)] || 'a'; return '(' + createUnarySafePrefix() + '(' + _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + '))';
return '( ' + assignee + createAssignment() + s + ')'; case 2:
assignee = getVarName();
return '(' + assignee + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')';
case 3:
assignee = getVarName();
expr = '(' + assignee + '[' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow)
+ ']' + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')';
return canThrow && rng(10) == 0 ? expr : '(' + assignee + ' && ' + expr + ')';
case 4:
assignee = getVarName();
expr = '(' + assignee + '.' + getDotKey() + createAssignment()
+ _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')';
return canThrow && rng(10) == 0 ? expr : '(' + assignee + ' && ' + expr + ')';
default:
return _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow);
} }
return s;
} }
function createTypeofExpr(recurmax, stmtDepth, canThrow) { function createTypeofExpr(recurmax, stmtDepth, canThrow) {
switch (rng(8)) { switch (rng(8)) {
case 0: case 0:
return 'typeof ' + createVarName(MANDATORY, DONT_STORE) + ' === "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '"'; return '(typeof ' + createVarName(MANDATORY, DONT_STORE) + ' === "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '")';
case 1: case 1:
return 'typeof ' + createVarName(MANDATORY, DONT_STORE) + ' !== "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '"'; return '(typeof ' + createVarName(MANDATORY, DONT_STORE) + ' !== "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '")';
case 2: case 2:
return 'typeof ' + createVarName(MANDATORY, DONT_STORE) + ' == "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '"'; return '(typeof ' + createVarName(MANDATORY, DONT_STORE) + ' == "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '")';
case 3: case 3:
return 'typeof ' + createVarName(MANDATORY, DONT_STORE) + ' != "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '"'; return '(typeof ' + createVarName(MANDATORY, DONT_STORE) + ' != "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '")';
case 4: case 4:
return 'typeof ' + createVarName(MANDATORY, DONT_STORE); return '(typeof ' + createVarName(MANDATORY, DONT_STORE) + ')';
default: default:
return '(typeof ' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ')'; return '(typeof ' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ')';
} }
@@ -603,16 +825,31 @@ function createAssignment() {
return ASSIGNMENTS[rng(ASSIGNMENTS.length)]; return ASSIGNMENTS[rng(ASSIGNMENTS.length)];
} }
function createUnaryOp() { function createUnarySafePrefix() {
return UNARY_OPS[rng(UNARY_OPS.length)]; return UNARY_SAFE[rng(UNARY_SAFE.length)];
}
function createUnaryPrefix() {
return UNARY_PREFIX[rng(UNARY_PREFIX.length)];
}
function createUnaryPostfix() {
return UNARY_POSTFIX[rng(UNARY_POSTFIX.length)];
}
function getVarName() {
// try to get a generated name reachable from current scope. default to just `a`
return VAR_NAMES[INITIAL_NAMES_LEN + rng(VAR_NAMES.length - INITIAL_NAMES_LEN)] || 'a';
} }
function createVarName(maybe, dontStore) { function createVarName(maybe, dontStore) {
if (!maybe || rng(2) === 1) { if (!maybe || rng(2)) {
var r = rng(VAR_NAMES.length); var name = VAR_NAMES[rng(VAR_NAMES.length)];
var suffixed = rng(5) > 0; var suffix = rng(3);
var name = VAR_NAMES[r] + (suffixed ? '_' + (++loops) : ''); if (suffix) {
if (!dontStore && suffixed) VAR_NAMES.push(name); name += '_' + suffix;
if (!dontStore) VAR_NAMES.push(name);
}
return name; return name;
} }
return ''; return '';
@@ -621,7 +858,6 @@ function createVarName(maybe, dontStore) {
function try_beautify(code, result) { function try_beautify(code, result) {
try { try {
var beautified = UglifyJS.minify(code, { var beautified = UglifyJS.minify(code, {
fromString: true,
compress: false, compress: false,
mangle: false, mangle: false,
output: { output: {
@@ -655,8 +891,8 @@ var default_options = {
mangle: { mangle: {
"cache": null, "cache": null,
"eval": false, "eval": false,
"ie8": false,
"keep_fnames": false, "keep_fnames": false,
"screw_ie8": true,
"toplevel": false, "toplevel": false,
}, },
output: infer_options(UglifyJS.OutputStream), output: infer_options(UglifyJS.OutputStream),
@@ -711,21 +947,28 @@ function log(options) {
} else { } else {
console.log("// !!! uglify failed !!!"); console.log("// !!! uglify failed !!!");
console.log(uglify_code.stack); console.log(uglify_code.stack);
if (typeof original_result != "string") {
console.log();
console.log();
console.log("original stacktrace:");
console.log(original_result.stack);
}
} }
console.log("minify(options):"); console.log("minify(options):");
options = JSON.parse(options); options = JSON.parse(options);
console.log(options); console.log(options);
console.log(); console.log();
if (!ok) { if (!ok && typeof uglify_code == "string") {
Object.keys(default_options).forEach(log_suspects.bind(null, options)); Object.keys(default_options).forEach(log_suspects.bind(null, options));
console.log("!!!!!! Failed... round", round); console.log("!!!!!! Failed... round", round);
} }
} }
var minify_options = require("./ufuzz.json").map(function(options) { var fallback_options = [ JSON.stringify({
options.fromString = true; compress: false,
return JSON.stringify(options); mangle: false
}); }) ];
var minify_options = require("./ufuzz.json").map(JSON.stringify);
var original_code, original_result; var original_code, original_result;
var uglify_code, uglify_result, ok; var uglify_code, uglify_result, ok;
for (var round = 1; round <= num_iterations; round++) { for (var round = 1; round <= num_iterations; round++) {
@@ -735,13 +978,9 @@ for (var round = 1; round <= num_iterations; round++) {
loops = 0; loops = 0;
funcs = 0; funcs = 0;
original_code = [ original_code = createTopLevelCode();
"var a = 100, b = 10, c = 0;", original_result = sandbox.run_code(original_code);
createTopLevelCode(), (typeof original_result != "string" ? fallback_options : minify_options).forEach(function(options) {
"console.log(null, a, b, c);" // preceding `null` makes for a cleaner output (empty string still shows up etc)
].join("\n");
minify_options.forEach(function(options) {
try { try {
uglify_code = UglifyJS.minify(original_code, JSON.parse(options)).code; uglify_code = UglifyJS.minify(original_code, JSON.parse(options)).code;
} catch (e) { } catch (e) {
@@ -750,11 +989,26 @@ for (var round = 1; round <= num_iterations; round++) {
ok = typeof uglify_code == "string"; ok = typeof uglify_code == "string";
if (ok) { if (ok) {
original_result = sandbox.run_code(original_code);
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") {
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);
if (!ok && isFinite(num_iterations)) process.exit(1); else if (verbose_error && typeof original_result != "string") {
console.log("//=============================================================");
console.log("// original code");
try_beautify(original_code, original_result);
console.log();
console.log();
console.log("original result:");
console.log(original_result);
console.log();
}
if (!ok && isFinite(num_iterations)) {
console.log();
process.exit(1);
}
}); });
} }
console.log();

View File

@@ -1,19 +1,4 @@
[ [
{
"compress": {
"warnings": false
}
},
{
"compress": {
"warnings": false
},
"mangle": false
},
{
"compress": false,
"mangle": true
},
{ {
"compress": false, "compress": false,
"mangle": false, "mangle": false,
@@ -22,12 +7,25 @@
"bracketize": true "bracketize": true
} }
}, },
{
"compress": false
},
{
"mangle": false
},
{},
{
"compress": {
"toplevel": true
},
"mangle": {
"toplevel": true
}
},
{ {
"compress": { "compress": {
"keep_fargs": false, "keep_fargs": false,
"passes": 3, "passes": 3
"pure_getters": true,
"warnings": false
} }
} }
] ]

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,10 +1,3 @@
// workaround for tty output truncation upon process.exit()
[process.stdout, process.stderr].forEach(function(stream){
if (stream._handle && stream._handle.setBlocking)
stream._handle.setBlocking(true);
});
var path = require("path");
var fs = require("fs"); var fs = require("fs");
var UglifyJS = exports; var UglifyJS = exports;
@@ -19,182 +12,25 @@ var FILES = UglifyJS.FILES = [
"../lib/sourcemap.js", "../lib/sourcemap.js",
"../lib/mozilla-ast.js", "../lib/mozilla-ast.js",
"../lib/propmangle.js", "../lib/propmangle.js",
"../lib/minify.js",
"./exports.js", "./exports.js",
].map(function(file){ ].map(function(file){
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.AST_Node.warn_function = function(txt) { function describe_ast() {
console.error("WARN: %s", txt); var out = OutputStream({ beautify: true });
};
function read_source_map(code) {
var match = /\n\/\/# sourceMappingURL=data:application\/json(;.*?)?;base64,(.*)/.exec(code);
if (!match) {
UglifyJS.AST_Node.warn("inline source map not found");
return null;
}
return JSON.parse(new Buffer(match[2], "base64"));
}
UglifyJS.minify = function(files, options) {
options = UglifyJS.defaults(options, {
compress : {},
fromString : false,
inSourceMap : null,
mangle : {},
mangleProperties : false,
nameCache : null,
outFileName : null,
output : null,
outSourceMap : null,
parse : {},
sourceMapInline : false,
sourceMapUrl : null,
sourceRoot : null,
spidermonkey : false,
warnings : false,
});
UglifyJS.base54.reset();
var inMap = options.inSourceMap;
if (typeof inMap == "string" && inMap != "inline") {
inMap = JSON.parse(fs.readFileSync(inMap, "utf8"));
}
// 1. parse
var toplevel = null,
sourcesContent = {};
if (options.spidermonkey) {
if (inMap == "inline") {
throw new Error("inline source map only works with built-in parser");
}
toplevel = UglifyJS.AST_Node.from_mozilla_ast(files);
} else {
function addFile(file, fileUrl) {
var code = options.fromString
? file
: fs.readFileSync(file, "utf8");
if (inMap == "inline") {
inMap = read_source_map(code);
}
sourcesContent[fileUrl] = code;
toplevel = UglifyJS.parse(code, {
filename: fileUrl,
toplevel: toplevel,
bare_returns: options.parse ? options.parse.bare_returns : undefined
});
}
if (!options.fromString) {
files = UglifyJS.simple_glob(files);
if (inMap == "inline" && files.length > 1) {
throw new Error("inline source map only works with singular input");
}
}
[].concat(files).forEach(function (files, i) {
if (typeof files === 'string') {
addFile(files, options.fromString ? i : files);
} else {
for (var fileUrl in files) {
addFile(files[fileUrl], fileUrl);
}
}
});
}
if (options.wrap) {
toplevel = toplevel.wrap_commonjs(options.wrap, options.exportAll);
}
// 2. compress
if (options.compress) {
var compress = { warnings: options.warnings };
UglifyJS.merge(compress, options.compress);
toplevel.figure_out_scope(options.mangle);
var sq = UglifyJS.Compressor(compress);
toplevel = sq.compress(toplevel);
}
// 3. mangle properties
if (options.mangleProperties || options.nameCache) {
options.mangleProperties.cache = UglifyJS.readNameCache(options.nameCache, "props");
toplevel = UglifyJS.mangle_properties(toplevel, options.mangleProperties);
UglifyJS.writeNameCache(options.nameCache, "props", options.mangleProperties.cache);
}
// 4. mangle
if (options.mangle) {
toplevel.figure_out_scope(options.mangle);
toplevel.compute_char_frequency(options.mangle);
toplevel.mangle_names(options.mangle);
}
// 5. output
var output = { max_line_len: 32000 };
if (options.outSourceMap || options.sourceMapInline) {
output.source_map = UglifyJS.SourceMap({
// prefer outFileName, otherwise use outSourceMap without .map suffix
file: options.outFileName || (typeof options.outSourceMap === 'string' ? options.outSourceMap.replace(/\.map$/i, '') : null),
orig: inMap,
root: options.sourceRoot
});
if (options.sourceMapIncludeSources) {
for (var file in sourcesContent) {
if (sourcesContent.hasOwnProperty(file)) {
output.source_map.get().setSourceContent(file, sourcesContent[file]);
}
}
}
}
if (options.output) {
UglifyJS.merge(output, options.output);
}
var stream = UglifyJS.OutputStream(output);
toplevel.print(stream);
var source_map = output.source_map;
if (source_map) {
source_map = source_map + "";
}
var mappingUrlPrefix = "\n//# sourceMappingURL=";
if (options.sourceMapInline) {
stream += mappingUrlPrefix + "data:application/json;charset=utf-8;base64," + new Buffer(source_map).toString("base64");
} else if (options.outSourceMap && typeof options.outSourceMap === "string" && options.sourceMapUrl !== false) {
stream += mappingUrlPrefix + (typeof options.sourceMapUrl === "string" ? options.sourceMapUrl : options.outSourceMap);
}
return {
code : stream + "",
map : source_map
};
};
// UglifyJS.describe_ast = function() {
// function doitem(ctor) {
// var sub = {};
// ctor.SUBCLASSES.forEach(function(ctor){
// sub[ctor.TYPE] = doitem(ctor);
// });
// var ret = {};
// if (ctor.SELF_PROPS.length > 0) ret.props = ctor.SELF_PROPS;
// if (ctor.SUBCLASSES.length > 0) ret.sub = sub;
// return ret;
// }
// return doitem(UglifyJS.AST_Node).sub;
// }
UglifyJS.describe_ast = function() {
var out = UglifyJS.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){
@@ -224,97 +60,6 @@ UglifyJS.describe_ast = function() {
}); });
} }
}; };
doitem(UglifyJS.AST_Node); doitem(AST_Node);
return out + ""; return out + "";
};
function readReservedFile(filename, reserved) {
if (!reserved) {
reserved = { vars: [], props: [] };
}
var data = fs.readFileSync(filename, "utf8");
data = JSON.parse(data);
if (data.vars) {
data.vars.forEach(function(name){
UglifyJS.push_uniq(reserved.vars, name);
});
}
if (data.props) {
data.props.forEach(function(name){
UglifyJS.push_uniq(reserved.props, name);
});
}
return reserved;
} }
UglifyJS.readReservedFile = readReservedFile;
UglifyJS.readDefaultReservedFile = function(reserved) {
return readReservedFile(require.resolve("./domprops.json"), reserved);
};
UglifyJS.readNameCache = function(filename, key) {
var cache = null;
if (filename) {
try {
var cache = fs.readFileSync(filename, "utf8");
cache = JSON.parse(cache)[key];
if (!cache) throw "init";
cache.props = UglifyJS.Dictionary.fromObject(cache.props);
} catch(ex) {
cache = {
cname: -1,
props: new UglifyJS.Dictionary()
};
}
}
return cache;
};
UglifyJS.writeNameCache = function(filename, key, cache) {
if (filename) {
var data;
try {
data = fs.readFileSync(filename, "utf8");
data = JSON.parse(data);
} catch(ex) {
data = {};
}
data[key] = {
cname: cache.cname,
props: cache.props.toObject()
};
fs.writeFileSync(filename, JSON.stringify(data, null, 2), "utf8");
}
};
// A file glob function that only supports "*" and "?" wildcards in the basename.
// Example: "foo/bar/*baz??.*.js"
// Argument `glob` may be a string or an array of strings.
// Returns an array of strings. Garbage in, garbage out.
UglifyJS.simple_glob = function simple_glob(glob) {
if (Array.isArray(glob)) {
return [].concat.apply([], glob.map(simple_glob));
}
if (glob.match(/\*|\?/)) {
var dir = path.dirname(glob);
try {
var entries = fs.readdirSync(dir);
} catch (ex) {}
if (entries) {
var pattern = "^" + path.basename(glob)
.replace(/[.+^$[\]\\(){}]/g, "\\$&")
.replace(/\*/g, "[^/\\\\]*")
.replace(/\?/g, "[^/\\\\]") + "$";
var mod = process.platform === "win32" ? "i" : "";
var rx = new RegExp(pattern, mod);
var results = entries.filter(function(name) {
return rx.test(name);
}).map(function(name) {
return path.join(dir, name);
});
if (results.length) return results;
}
}
return [ glob ];
};