Compare commits

...

56 Commits

Author SHA1 Message Date
Mihai Bazon
c6fa291571 v2.4.23 2015-05-20 17:48:30 +03:00
Mihai Bazon
bce4307e9e Treat \uFEFF as whitespace.
Fix #714
2015-05-20 16:17:46 +03:00
Mihai Bazon
96ad94ab41 v2.4.22 2015-05-18 13:58:25 +03:00
Mihai Bazon
a5b60217ce Fix compressing conditionals
Only transform foo() ? EXP(x) : EXP(y) into EXP(foo() ? x : y) if EXP has no
side effects.

Fix #710
2015-05-18 13:56:04 +03:00
Mihai Bazon
44fd6694eb fix again reserved props 2015-05-13 22:03:00 +03:00
Mihai Bazon
e48db3a8b6 Make reserved names take priority over the name cache 2015-05-07 15:01:16 +03:00
Mihai Bazon
e637bdaf4e Only drop the BOM when it's the first character.
Close #704
2015-05-05 10:11:38 +03:00
Mihai Bazon
d558abbdb7 v2.4.21 2015-05-04 19:14:42 +03:00
Mihai Bazon
4aed0830e5 Fix blank lines in the output.
The issue was more obvious when max_line_len has a small value, rather than
the default 32K characters.  A blank line showed up after most statements.
2015-05-04 17:55:42 +03:00
Mihai Bazon
d2dda34b2a Remove deprecated calls to utils.print/utils.error
Close #542, #641, #647
2015-05-04 15:07:16 +03:00
Mihai Bazon
c3a10c135e Avoid spurious brackets when dropping unused vars
Fix #702
2015-05-04 14:49:17 +03:00
Mihai Bazon
92e4340732 Fix parsing strings with literal DOS newlines
(should not set newline_before)

Fix #693
2015-04-23 12:08:19 +03:00
Mihai Bazon
7b22f2031f If name_cache is specified, do rename cached properties
(even if --mangle-props is not there)
2015-04-22 17:34:49 +03:00
Mihai Bazon
3b14582d6b Fix tests 2015-04-17 11:28:59 +03:00
Mihai Bazon
274e1b3dc7 Drop NaN -> 0/0 transformation.
Fix #687
2015-04-17 11:26:36 +03:00
Fábio Santos
de58b0289d Added expect_exact for testing the OutputStream
This works almost exactly like `expect`, except that you pass a literal string
of which the result is compared with the generated output.
2015-04-14 20:26:47 +02:00
XhmikosR
efea52a4f4 Normalize package.json.
* Specify the files to install in package.json
* Add missing properties
* Follow `npm init`'s scheme
2015-04-14 20:17:03 +02:00
Jordan Harband
763bd36b60 Test on latest node and io.js
Per 0262b4244c - if you're going to stop testing on 0.8, you should be testing on 0.12.

Also allow failures on unstable nodes and "older than two latest" `io.js` versions, and enable "sudo: false" which makes tests run faster.
2015-04-14 20:06:09 +02:00
Mihai Bazon
0552dbd93c v2.4.20 2015-04-13 18:59:21 +03:00
Mihai Bazon
18c63ff3d8 Fix compression of conditionals
Don't move the condition on the right side of an assignment when
the left side may have side effects.

Fix #677
2015-04-13 17:29:48 +03:00
Mihai Bazon
e04ef56243 Use the before visitor in mangle props
(works around a bug in our tree walker which, while cloning nodes, breaks
references between labeled statements and break/continue labels)
2015-04-10 11:33:29 +03:00
Mihai Bazon
5d60484553 More fixes for the breaking changes in yargs
Close #670
2015-04-05 13:20:22 +03:00
Mihai Bazon
3c846e6f7b Merge pull request #669 from galvanix/documentation-inSourceMap
Document passing source maps directly to minify() using inSourceMap
2015-04-04 15:29:03 +03:00
David Caldwell
2850dc69fd Document passing source maps directly to minify() using inSourceMap 2015-04-03 17:27:28 -07:00
Mihai Bazon
94205c3a37 v2.4.19 2015-03-29 14:02:37 +03:00
Mihai Bazon
2ada34b229 Merge pull request #660 from ntkme/fix-long-options
Fix long options
2015-03-29 14:01:21 +03:00
なつき
db396da734 Fix long options 2015-03-29 04:00:42 -07:00
Mihai Bazon
0262b4244c Disable testing with Node 0.8 2015-03-29 13:19:07 +03:00
Mihai Bazon
73ca767d06 v2.4.18 2015-03-29 13:15:27 +03:00
Mihai Bazon
3ec11c781b Update README 2015-03-29 13:13:40 +03:00
Mihai Bazon
a79ff060d0 Merge branch 'propmangle' 2015-03-29 12:50:43 +03:00
Mihai Bazon
43991f8d2f Add tool to extract property names 2015-03-29 12:38:06 +03:00
Mihai Bazon
6b82069e1a Merge in more DOM properties. 2015-03-24 13:59:57 +02:00
Mihai Bazon
276b9a31cd Fix compressing ![foo()]; as a statement
need to check whether the literal has any side effects before replacing that
with `false`.
2015-03-23 23:27:00 +02:00
Mihai Bazon
5801fa39e9 [sequencesize] Actually even better:
do create the sequence even if the stat list is bigger than 2000 statements,
but limit the sequence itself to 2000 expressions.

Ref #414
2015-03-22 13:02:45 +02:00
Mihai Bazon
f0ab1b02e6 Avoid sequencesize for more than 2000 statements.
It hardly saves any bytes for a sequence so long, and it risks blowing the
stack with the recursive Seq functions.

Ref #414
2015-03-22 12:51:15 +02:00
Mihai Bazon
36c28e02fd Add start/end nodes for NaN/Infinity transformations 2015-03-22 12:50:36 +02:00
Mihai Bazon
e1c3861832 Export readDefaultReservedFile 2015-03-22 11:04:28 +02:00
Mihai Bazon
ecfd881ac6 Keep unused function arguments by default
Discarding unused function arguments affects function.length, which can lead
to some hard to debug issues.  This optimization is now done only in "unsafe
mode".

Fix #121
2015-03-20 10:28:51 +02:00
Mihai Bazon
81b7335267 Don't use Object.create 2015-03-19 12:54:43 +02:00
Mihai Bazon
bb010c2253 tools/props.html: output complete JSON 2015-03-19 10:10:01 +02:00
Mihai Bazon
03b6121194 Add --reserve-domprops along with a default exclusion list in tools/domprops.json 2015-03-18 12:10:21 +02:00
Mihai Bazon
3ef092332b Support multiple --reserved-file args 2015-03-18 11:53:17 +02:00
Mihai Bazon
540c19792f Bump yargs version (for .array arguments) 2015-03-18 11:51:09 +02:00
Mihai Bazon
80d1c8206b Fix parsing for U+2028 / U+2029
(they should be treated as whitespace)
2015-03-18 10:11:37 +02:00
Mihai Bazon
d36faffeca Fix parsing for U+2028 / U+2029
(they should be treated as whitespace)
2015-03-18 10:09:30 +02:00
Mihai Bazon
7c8c9b94bc tools/props.html: use try/catch in a few more places 2015-03-17 14:31:22 +02:00
Mihai Bazon
f5eeed7665 Add tool to list DOM properties/methods 2015-03-17 11:46:04 +02:00
Mihai Bazon
80cfd063e2 Export readNameCache / writeNameCache 2015-03-17 10:05:49 +02:00
Mihai Bazon
aa45f6586e rename --prop-cache to --name-cache
... and support storing there variable names as well, to help with multiple
invocations when mangling toplevel.
2015-03-16 13:16:30 +02:00
Mihai Bazon
0c80d21e01 Fix prop mangling
Even if not “defined”, do mangle if name exists in the cache.
2015-03-16 10:53:31 +02:00
Mihai Bazon
375c88245a Fix --reserved-file 2015-03-14 11:36:58 +02:00
Mihai Bazon
ea3430102c Add property name mangler
We only touch properties that are present in an object literal, or which are
assigned to.  Example:

    x = { foo: 1 };
    x.bar = 2;
    x["baz"] = 3;
    x[cond ? "qwe" : "asd"] = 4;
    console.log(x.stuff);

The names "foo", "bar", "baz", "qwe" and "asd" will be mangled, and the
resulting mangled names will be used for the same properties throughout the
code.  The "stuff" will not be, since it's just referenced but never
assigned to.

This *will* break most of the code out there, but could work on carefully
written code: do not use eval, do not define methods or properties by
walking an array of names, etc.  Also, a comprehensive list of exclusions
needs to be passed, to avoid mangling properties that are standard in
JavaScript, DOM, used in external libraries etc.
2015-03-14 11:22:28 +02:00
Mihai Bazon
9de7199b88 v2.4.17 2015-03-11 00:04:26 +02:00
Edward Casbon
ae07714927 Add filename to the JS_Parse_Error exception.
It would be nice to have access to the filename of the file that includes the code that causes a JavaScript error. This is especially handy if uglifying multiple files at once.

Only a small change is needed for this to happen as it's already available in the function that throws the error.
2015-02-11 23:21:22 +01:00
Rob Loach
0e41a3fad4 Add .npmignore 2015-02-11 23:19:21 +01:00
19 changed files with 6425 additions and 174 deletions

3
.gitignore vendored
View File

@@ -1,2 +1,3 @@
/node_modules/
/npm-debug.log
tmp/
node_modules/

View File

@@ -1,6 +1,10 @@
language: node_js
before_install: "npm install -g npm"
node_js:
- "0.8"
- "0.10"
- "iojs"
- "0.12"
- "0.11"
- "0.10"
matrix:
fast_finish: true
sudo: false

223
README.md
View File

@@ -52,70 +52,87 @@ a double dash to prevent input files being used as option arguments:
The available options are:
```
--source-map Specify an output file where to generate source map.
[string]
--source-map-root The path to the original source to be included in the
source map. [string]
--source-map-url The path to the source map to be added in //#
sourceMappingURL. Defaults to the value passed with
--source-map. [string]
--source-map-include-sources
Pass this flag if you want to include the content of
source files in the source map as sourcesContent
property. [boolean]
--in-source-map Input source map, useful if you're compressing JS that was
generated from some other original code.
--screw-ie8 Pass this flag if you don't care about full compliance
with Internet Explorer 6-8 quirks (by default UglifyJS
will try to be IE-proof). [boolean]
--expr Parse a single expression, rather than a program (for
parsing JSON) [boolean]
-p, --prefix 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. [string]
-o, --output Output file (default STDOUT).
-b, --beautify Beautify output/specify output options. [string]
-m, --mangle Mangle names/pass mangler options. [string]
-r, --reserved Reserved names to exclude from mangling.
-c, --compress 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. [string]
-d, --define Global definitions [string]
-e, --enclose Embed everything in a big function, with a configurable
parameter/argument list. [string]
--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:
- "all" to keep all comments
- a valid JS regexp (needs to start with a slash) to keep
only comments that match.
Note that currently not *all* comments can be kept when
compression is on, because of dead code removal or
cascading statements into sequences. [string]
--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.
--stats Display operations run time on STDERR. [boolean]
--acorn Use Acorn for parsing. [boolean]
--spidermonkey Assume input files are SpiderMonkey AST format (as JSON).
[boolean]
--self Build itself (UglifyJS2) as a library (implies
--wrap=UglifyJS --export-all) [boolean]
--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.
[string]
--export-all Only used when --wrap, this tells UglifyJS to add code to
automatically export all globals. [boolean]
--lint Display some scope warnings [boolean]
-v, --verbose Verbose [boolean]
-V, --version Print version number and exit. [boolean]
--source-map Specify an output file where to generate source
map.
--source-map-root The path to the original source to be included
in the source map.
--source-map-url The path to the source map to be added in //#
sourceMappingURL. Defaults to the value passed
with --source-map.
--source-map-include-sources Pass this flag if you want to include the
content of source files in the source map as
sourcesContent property.
--in-source-map Input source map, useful if you're compressing
JS that was generated from some other original
code.
--screw-ie8 Pass this flag if you don't care about full
compliance with Internet Explorer 6-8 quirks
(by default UglifyJS will try to be IE-proof).
--expr Parse a single expression, rather than a
program (for parsing JSON)
-p, --prefix 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.
-o, --output Output file (default STDOUT).
-b, --beautify Beautify output/specify output options.
-m, --mangle Mangle names/pass mangler options.
-r, --reserved Reserved names to exclude from mangling.
-c, --compress 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.
-d, --define Global definitions
-e, --enclose Embed everything in a big function, with a
configurable parameter/argument list.
--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:
- "all" to keep all comments
- a valid JS regexp (needs to start with a
slash) to keep only comments that match.
Note that currently not *all* comments can be
kept when compression is on, because of dead
code removal or cascading statements into
sequences.
--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.
--stats Display operations run time on STDERR.
--acorn Use Acorn for parsing.
--spidermonkey Assume input files are SpiderMonkey AST format
(as JSON).
--self Build itself (UglifyJS2) as a library (implies
--wrap=UglifyJS --export-all)
--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.
--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.
--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
--name-cache File to hold mangled names mappings
```
Specify `--output` (`-o`) to declare the output file. Otherwise the output
@@ -189,6 +206,69 @@ comma-separated list of names. For example:
to prevent the `require`, `exports` and `$` names from being changed.
### Mangling property names (`--mangle-props`)
**Note:** this will probably break your code. Mangling property names is a
separate step, different from variable name mangling. Pass
`--mangle-props`. It will mangle all properties that are seen in some
object literal, or that are assigned to. For example:
```js
var x = {
foo: 1
};
x.bar = 2;
x["baz"] = 3;
x[condition ? "moo" : "boo"] = 4;
console.log(x.something());
```
In the above code, `foo`, `bar`, `baz`, `moo` and `boo` will be replaced
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
names. For instance, if your code would contain `x.length = 10`, then
`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
cover most standard JS and DOM properties defined in various browsers. Pass
`--reserve-domprops` to read that in.
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
mangled to the same name in all of them. For this, pass `--name-cache
filename.json` and UglifyJS will maintain these mappings in a file which can
then be reused. It should be initially empty. Example:
```
rm -f /tmp/cache.json # start fresh
uglifyjs file1.js file2.js --mangle-props --name-cache /tmp/cache.json -o part1.js
uglifyjs file3.js file4.js --mangle-props --name-cache /tmp/cache.json -o part2.js
```
Now, `part1.js` and `part2.js` will be consistent with each other in terms
of mangled property names.
Using the name cache is not necessary if you compress all your files in a
single call to UglifyJS.
## Compressor options
You need to pass `--compress` (`-c`) to enable the compressor. Optionally
@@ -272,14 +352,15 @@ contrived cases, but should be fine for most code. You might want to try it
on your own code, it should reduce the minified size. Here's what happens
when this flag is on:
- `new Array(1, 2, 3)` or `Array(1, 2, 3)` → `[1, 2, 3 ]`
- `new Array(1, 2, 3)` or `Array(1, 2, 3)` → `[ 1, 2, 3 ]`
- `new Object()` → `{}`
- `String(exp)` or `exp.toString()` → `"" + exp`
- `new Object/RegExp/Function/Error/Array (...)` → we discard the `new`
- `typeof foo == "undefined"` → `foo === void 0`
- `void 0` → `undefined` (if there is a variable named "undefined" in
scope; we do it because the variable name will be mangled, typically
reduced to a single character).
reduced to a single character)
- discards unused function arguments (affects `function.length`)
### Conditional compilation
@@ -510,6 +591,16 @@ var result = UglifyJS.minify("compiled.js", {
// 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
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).

77
bin/extract-props.js Executable file
View File

@@ -0,0 +1,77 @@
#! /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

@@ -67,6 +67,10 @@ You need to pass an argument to this option to specify the name that your module
.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")
.describe("name-cache", "File to hold mangled names mappings")
.alias("p", "prefix")
.alias("o", "output")
@@ -84,13 +88,21 @@ You need to pass an argument to this option to specify the name that your module
.string("source-map-root")
.string("source-map-url")
.string("b")
.string("beautify")
.string("m")
.string("mangle")
.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")
.boolean("expr")
.boolean("source-map-include-sources")
@@ -98,14 +110,18 @@ You need to pass an argument to this option to specify the name that your module
.boolean("export-all")
.boolean("self")
.boolean("v")
.boolean("verbose")
.boolean("stats")
.boolean("acorn")
.boolean("spidermonkey")
.boolean("lint")
.boolean("V")
.boolean("version")
.boolean("noerr")
.boolean("bare-returns")
.boolean("keep-fnames")
.boolean("mangle-props")
.boolean("reserve-domprops")
.wrap(80)
@@ -116,24 +132,24 @@ normalize(ARGS);
if (ARGS.noerr) {
UglifyJS.DefaultsError.croak = function(msg, defs) {
sys.error("WARN: " + msg);
print_error("WARN: " + msg);
};
}
if (ARGS.version || ARGS.V) {
var json = require("../package.json");
sys.puts(json.name + ' ' + json.version);
print(json.name + ' ' + json.version);
process.exit(0);
}
if (ARGS.ast_help) {
var desc = UglifyJS.describe_ast();
sys.puts(typeof desc == "string" ? desc : JSON.stringify(desc, null, 2));
print(typeof desc == "string" ? desc : JSON.stringify(desc, null, 2));
process.exit(0);
}
if (ARGS.h || ARGS.help) {
sys.puts(yargs.help());
print(yargs.help());
process.exit(0);
}
@@ -144,6 +160,15 @@ if (ARGS.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) {
RESERVED = UglifyJS.readDefaultReservedFile(RESERVED);
}
if (ARGS.d) {
if (COMPRESS) COMPRESS.global_defs = getOptions("d");
@@ -153,6 +178,19 @@ 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);
}
if (ARGS.quotes === true) {
ARGS.quotes = 3;
}
@@ -177,13 +215,13 @@ if (ARGS.keep_fnames) {
if (BEAUTIFY)
UglifyJS.merge(OUTPUT_OPTIONS, BEAUTIFY);
if (ARGS.comments) {
if (ARGS.comments != null) {
if (/^\/.*\/[a-zA-Z]*$/.test(ARGS.comments)) {
var regex_pos = ARGS.comments.lastIndexOf("/");
try {
OUTPUT_OPTIONS.comments = new RegExp(ARGS.comments.substr(1, regex_pos - 1), ARGS.comments.substr(regex_pos + 1));
} catch (e) {
sys.error("ERROR: Invalid --comments: " + e.message);
print_error("ERROR: Invalid --comments: " + e.message);
}
} else if (ARGS.comments == "all") {
OUTPUT_OPTIONS.comments = true;
@@ -203,7 +241,7 @@ var files = ARGS._.slice();
if (ARGS.self) {
if (files.length > 0) {
sys.error("WARN: Ignoring input files since --self was passed");
print_error("WARN: Ignoring input files since --self was passed");
}
files = UglifyJS.FILES;
if (!ARGS.wrap) ARGS.wrap = "UglifyJS";
@@ -215,7 +253,7 @@ var ORIG_MAP = ARGS.in_source_map;
if (ORIG_MAP) {
ORIG_MAP = JSON.parse(fs.readFileSync(ORIG_MAP));
if (files.length == 0) {
sys.error("INFO: Using file from the input source map: " + ORIG_MAP.file);
print_error("INFO: Using file from the input source map: " + ORIG_MAP.file);
files = [ ORIG_MAP.file ];
}
if (ARGS.source_map_root == null) {
@@ -228,12 +266,12 @@ if (files.length == 0) {
}
if (files.indexOf("-") >= 0 && ARGS.source_map) {
sys.error("ERROR: Source map doesn't work with input from STDIN");
print_error("ERROR: Source map doesn't work with input from STDIN");
process.exit(1);
}
if (files.filter(function(el){ return el == "-" }).length > 1) {
sys.error("ERROR: Can read a single file from STDIN (two or more dashes specified)");
print_error("ERROR: Can read a single file from STDIN (two or more dashes specified)");
process.exit(1);
}
@@ -256,9 +294,9 @@ try {
var compressor = COMPRESS && UglifyJS.Compressor(COMPRESS);
} catch(ex) {
if (ex instanceof UglifyJS.DefaultsError) {
sys.error(ex.msg);
sys.error("Supported options:");
sys.error(sys.inspect(ex.defs));
print_error(ex.msg);
print_error("Supported options:");
print_error(sys.inspect(ex.defs));
process.exit(1);
}
}
@@ -266,7 +304,7 @@ try {
async.eachLimit(files, 1, function (file, cb) {
read_whole_file(file, function (err, code) {
if (err) {
sys.error("ERROR: can't read file: " + file);
print_error("ERROR: can't read file: " + file);
process.exit(1);
}
if (ARGS.p != null) {
@@ -303,9 +341,9 @@ async.eachLimit(files, 1, function (file, cb) {
});
} catch(ex) {
if (ex instanceof UglifyJS.JS_Parse_Error) {
sys.error("Parse error at " + file + ":" + ex.line + "," + ex.col);
sys.error(ex.message);
sys.error(ex.stack);
print_error("Parse error at " + file + ":" + ex.line + "," + ex.col);
print_error(ex.message);
print_error(ex.stack);
process.exit(1);
}
throw ex;
@@ -319,11 +357,11 @@ async.eachLimit(files, 1, function (file, cb) {
TOPLEVEL = UglifyJS.AST_Node.from_mozilla_ast(TOPLEVEL);
});
if (ARGS.wrap) {
if (ARGS.wrap != null) {
TOPLEVEL = TOPLEVEL.wrap_commonjs(ARGS.wrap, ARGS.export_all);
}
if (ARGS.enclose) {
if (ARGS.enclose != null) {
var arg_parameter_list = ARGS.enclose;
if (arg_parameter_list === true) {
arg_parameter_list = [];
@@ -334,11 +372,23 @@ async.eachLimit(files, 1, function (file, cb) {
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");
TOPLEVEL = UglifyJS.mangle_properties(TOPLEVEL, {
reserved : reserved,
cache : cache,
only_cache : !ARGS.mangle_props
});
writeNameCache("props", cache);
})();
var SCOPE_IS_NEEDED = COMPRESS || MANGLE || ARGS.lint;
var TL_CACHE = readNameCache("vars");
if (SCOPE_IS_NEEDED) {
time_it("scope", function(){
TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8 });
TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8, cache: TL_CACHE });
if (ARGS.lint) {
TOPLEVEL.scope_warnings();
}
@@ -353,17 +403,20 @@ async.eachLimit(files, 1, function (file, cb) {
if (SCOPE_IS_NEEDED) {
time_it("scope", function(){
TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8 });
if (MANGLE) {
TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8, cache: TL_CACHE });
if (MANGLE && !TL_CACHE) {
TOPLEVEL.compute_char_frequency(MANGLE);
}
});
}
if (MANGLE) time_it("mangle", function(){
MANGLE.cache = TL_CACHE;
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)) {
@@ -391,15 +444,15 @@ async.eachLimit(files, 1, function (file, cb) {
if (OUTPUT_FILE) {
fs.writeFileSync(OUTPUT_FILE, output, "utf8");
} else {
sys.print(output);
print(output);
}
if (ARGS.stats) {
sys.error(UglifyJS.string_template("Timing information (compressed {count} files):", {
print_error(UglifyJS.string_template("Timing information (compressed {count} files):", {
count: files.length
}));
for (var i in STATS) if (STATS.hasOwnProperty(i)) {
sys.error(UglifyJS.string_template("- {name}: {time}s", {
print_error(UglifyJS.string_template("- {name}: {time}s", {
name: i,
time: (STATS[i] / 1000).toFixed(3)
}));
@@ -418,15 +471,15 @@ function normalize(o) {
function getOptions(x, constants) {
x = ARGS[x];
if (!x) return null;
if (x == null) return null;
var ret = {};
if (x !== true) {
if (x !== "") {
var ast;
try {
ast = UglifyJS.parse(x, { expression: true });
} catch(ex) {
if (ex instanceof UglifyJS.JS_Parse_Error) {
sys.error("Error parsing arguments in: " + x);
print_error("Error parsing arguments in: " + x);
process.exit(1);
}
}
@@ -445,8 +498,8 @@ function getOptions(x, constants) {
ret[name] = true;
return true; // no descend
}
sys.error(node.TYPE)
sys.error("Error parsing arguments in: " + x);
print_error(node.TYPE)
print_error("Error parsing arguments in: " + x);
process.exit(1);
}));
}
@@ -478,3 +531,11 @@ function time_it(name, cont) {
}
return ret;
}
function print_error(msg) {
console.error("%s", msg);
}
function print(txt) {
console.log("%s", txt);
}

View File

@@ -489,7 +489,7 @@ merge(Compressor.prototype, {
seq = [];
};
statements.forEach(function(stat){
if (stat instanceof AST_SimpleStatement) seq.push(stat.body);
if (stat instanceof AST_SimpleStatement && seq.length < 2000) seq.push(stat.body);
else push_seq(), ret.push(stat);
});
push_seq();
@@ -1086,7 +1086,7 @@ merge(Compressor.prototype, {
var tt = new TreeTransformer(
function before(node, descend, in_list) {
if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) {
if (!compressor.option("keep_fargs")) {
if (compressor.option("unsafe") && !compressor.option("keep_fargs")) {
for (var a = node.argnames, i = a.length; --i >= 0;) {
var sym = a[i];
if (sym.unreferenced()) {
@@ -1168,12 +1168,12 @@ merge(Compressor.prototype, {
return make_node(AST_EmptyStatement, node);
}
if (def.length == 0) {
return side_effects;
return in_list ? MAP.splice(side_effects.body) : side_effects;
}
node.definitions = def;
if (side_effects) {
side_effects.body.unshift(node);
node = side_effects;
return in_list ? MAP.splice(side_effects.body) : side_effects;
}
return node;
}
@@ -2267,16 +2267,8 @@ merge(Compressor.prototype, {
OPT(AST_Infinity, function (self, compressor) {
return make_node(AST_Binary, self, {
operator : '/',
left : make_node(AST_Number, null, {value: 1}),
right : make_node(AST_Number, null, {value: 0})
});
});
OPT(AST_NaN, function (self, compressor) {
return make_node(AST_Binary, self, {
operator : '/',
left : make_node(AST_Number, null, {value: 0}),
right : make_node(AST_Number, null, {value: 0})
left : make_node(AST_Number, self, {value: 1}),
right : make_node(AST_Number, self, {value: 0})
});
});
@@ -2343,6 +2335,7 @@ merge(Compressor.prototype, {
&& alternative instanceof AST_Assign
&& consequent.operator == alternative.operator
&& consequent.left.equivalent_to(alternative.left)
&& !consequent.left.has_side_effects(compressor)
) {
/*
* Stuff like this:
@@ -2363,6 +2356,7 @@ merge(Compressor.prototype, {
if (consequent instanceof AST_Call
&& alternative.TYPE === consequent.TYPE
&& consequent.args.length == alternative.args.length
&& !consequent.expression.has_side_effects(compressor)
&& consequent.expression.equivalent_to(alternative.expression)) {
if (consequent.args.length == 0) {
return make_node(AST_Seq, self, {
@@ -2480,7 +2474,7 @@ merge(Compressor.prototype, {
});
function literals_in_boolean_context(self, compressor) {
if (compressor.option("booleans") && compressor.in_boolean_context()) {
if (compressor.option("booleans") && compressor.in_boolean_context() && !self.has_side_effects(compressor)) {
return make_node(AST_True, self);
}
return self;

View File

@@ -176,7 +176,6 @@ function OutputStream(options) {
might_need_space = false;
}
might_need_semicolon = false;
maybe_newline();
}
if (!options.beautify && options.preserve_line && stack[stack.length - 1]) {

View File

@@ -108,7 +108,7 @@ var OPERATORS = makePredicate([
"||"
]);
var WHITESPACE_CHARS = makePredicate(characters(" \u00a0\n\r\t\f\u000b\u200b\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000"));
var WHITESPACE_CHARS = makePredicate(characters(" \u00a0\n\r\t\f\u000b\u200b\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\uFEFF"));
var PUNC_BEFORE_EXPRESSION = makePredicate(characters("[{(,.;:"));
@@ -187,8 +187,9 @@ function parse_js_number(num) {
}
};
function JS_Parse_Error(message, line, col, pos) {
function JS_Parse_Error(message, filename, line, col, pos) {
this.message = message;
this.filename = filename;
this.line = line;
this.col = col;
this.pos = pos;
@@ -200,7 +201,7 @@ JS_Parse_Error.prototype.toString = function() {
};
function js_error(message, filename, line, col, pos) {
throw new JS_Parse_Error(message, line, col, pos);
throw new JS_Parse_Error(message, filename, line, col, pos);
};
function is_token(token, type, val) {
@@ -212,7 +213,7 @@ var EX_EOF = {};
function tokenizer($TEXT, filename, html5_comments) {
var S = {
text : $TEXT.replace(/\uFEFF/g, ''),
text : $TEXT,
filename : filename,
pos : 0,
tokpos : 0,
@@ -297,7 +298,8 @@ function tokenizer($TEXT, filename, html5_comments) {
};
function skip_whitespace() {
while (WHITESPACE_CHARS(peek()))
var ch;
while (WHITESPACE_CHARS(ch = peek()) || ch == "\u2028" || ch == "\u2029")
next();
};
@@ -352,8 +354,13 @@ function tokenizer($TEXT, filename, html5_comments) {
case 120 : return String.fromCharCode(hex_bytes(2)); // \x
case 117 : return String.fromCharCode(hex_bytes(4)); // \u
case 10 : return ""; // newline
default : return ch;
case 13 : // \r
if (peek() == "\n") { // DOS newline
next(true, in_string);
return "";
}
}
return ch;
};
function hex_bytes(n) {
@@ -370,7 +377,7 @@ function tokenizer($TEXT, filename, html5_comments) {
var read_string = with_eof_error("Unterminated string constant", function(quote_char){
var quote = next(), ret = "";
for (;;) {
var ch = next(true);
var ch = next(true, true);
if (ch == "\\") {
// read OctalEscapeSequence (XXX: deprecated if "strict mode")
// https://github.com/mishoo/UglifyJS/issues/178

217
lib/propmangle.js Normal file
View File

@@ -0,0 +1,217 @@
/***********************************************************************
A JavaScript tokenizer / parser / beautifier / compressor.
https://github.com/mishoo/UglifyJS2
-------------------------------- (C) ---------------------------------
Author: Mihai Bazon
<mihai.bazon@gmail.com>
http://mihai.bazon.net/blog
Distributed under the BSD license:
Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the following
disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
***********************************************************************/
"use strict";
function find_builtins() {
var a = [];
[ Object, Array, Function, Number,
String, Boolean, Error, Math,
Date, RegExp
].forEach(function(ctor){
Object.getOwnPropertyNames(ctor).map(add);
if (ctor.prototype) {
Object.getOwnPropertyNames(ctor.prototype).map(add);
}
});
function add(name) {
push_uniq(a, name);
}
return a;
}
function mangle_properties(ast, options) {
options = defaults(options, {
reserved : null,
cache : null,
only_cache : false
});
var reserved = options.reserved;
if (reserved == null)
reserved = find_builtins();
var cache = options.cache;
if (cache == null) {
cache = {
cname: -1,
props: new Dictionary()
};
}
var names_to_mangle = [];
// step 1: find candidates to mangle
ast.walk(new TreeWalker(function(node){
if (node instanceof AST_ObjectKeyVal) {
add(node.key);
}
else if (node instanceof AST_ObjectProperty) {
// setter or getter, since KeyVal is handled above
add(node.key.name);
}
else if (node instanceof AST_Dot) {
if (this.parent() instanceof AST_Assign) {
add(node.property);
}
}
else if (node instanceof AST_Sub) {
if (this.parent() instanceof AST_Assign) {
addStrings(node.property);
}
}
}));
// step 2: transform the tree, renaming properties
return ast.transform(new TreeTransformer(function(node){
if (node instanceof AST_ObjectKeyVal) {
if (should_mangle(node.key)) {
node.key = mangle(node.key);
}
}
else if (node instanceof AST_ObjectProperty) {
// setter or getter
if (should_mangle(node.key.name)) {
node.key.name = mangle(node.key.name);
}
}
else if (node instanceof AST_Dot) {
if (should_mangle(node.property)) {
node.property = mangle(node.property);
}
}
else if (node instanceof AST_Sub) {
node.property = mangleStrings(node.property);
}
// else if (node instanceof AST_String) {
// if (should_mangle(node.value)) {
// AST_Node.warn(
// "Found \"{prop}\" property candidate for mangling in an arbitrary string [{file}:{line},{col}]", {
// file : node.start.file,
// line : node.start.line,
// col : node.start.col,
// prop : node.value
// }
// );
// }
// }
}));
// only function declarations after this line
function can_mangle(name) {
if (reserved.indexOf(name) >= 0) return false;
if (options.only_cache) {
return cache.props.has(name);
}
if (/^[0-9.]+$/.test(name)) return false;
return true;
}
function should_mangle(name) {
if (reserved.indexOf(name) >= 0) return false;
return cache.props.has(name)
|| names_to_mangle.indexOf(name) >= 0;
}
function add(name) {
if (can_mangle(name))
push_uniq(names_to_mangle, name);
}
function mangle(name) {
var mangled = cache.props.get(name);
if (!mangled) {
do {
mangled = base54(++cache.cname);
} while (!can_mangle(mangled));
cache.props.set(name, mangled);
}
return mangled;
}
function addStrings(node) {
var out = {};
try {
(function walk(node){
node.walk(new TreeWalker(function(node){
if (node instanceof AST_Seq) {
walk(node.cdr);
return true;
}
if (node instanceof AST_String) {
add(node.value);
return true;
}
if (node instanceof AST_Conditional) {
walk(node.consequent);
walk(node.alternative);
return true;
}
throw out;
}));
})(node);
} catch(ex) {
if (ex !== out) throw ex;
}
}
function mangleStrings(node) {
return node.transform(new TreeTransformer(function(node){
if (node instanceof AST_Seq) {
node.cdr = mangleStrings(node.cdr);
}
else if (node instanceof AST_String) {
if (should_mangle(node.value)) {
node.value = mangle(node.value);
}
}
else if (node instanceof AST_Conditional) {
node.consequent = mangleStrings(node.consequent);
node.alternative = mangleStrings(node.alternative);
}
return node;
}));
}
}

View File

@@ -67,18 +67,26 @@ SymbolDef.prototype = {
|| this.orig[0] instanceof AST_SymbolDefun));
},
mangle: function(options) {
if (!this.mangled_name && !this.unmangleable(options)) {
var cache = options.cache && options.cache.props;
if (this.global && cache && cache.has(this.name)) {
this.mangled_name = cache.get(this.name);
}
else if (!this.mangled_name && !this.unmangleable(options)) {
var s = this.scope;
if (!options.screw_ie8 && this.orig[0] instanceof AST_SymbolLambda)
s = s.parent_scope;
this.mangled_name = s.next_mangled(options, this);
if (this.global && cache) {
cache.set(this.name, this.mangled_name);
}
}
}
};
AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
options = defaults(options, {
screw_ie8: false
screw_ie8: false,
cache: null
});
// pass 1: setup scope chaining and handle definitions
@@ -183,6 +191,10 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
}
});
self.walk(tw);
if (options.cache) {
this.cname = options.cache.cname;
}
});
AST_Scope.DEFMETHOD("init_scope_vars", function(nesting){
@@ -344,6 +356,15 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
// the AST_SymbolDeclaration that it points to).
var lname = -1;
var to_mangle = [];
if (options.cache) {
this.globals.each(function(symbol){
if (options.except.indexOf(symbol.name) < 0) {
to_mangle.push(symbol);
}
});
}
var tw = new TreeWalker(function(node, descend){
if (node instanceof AST_LabeledStatement) {
// lname is incremented when we get to the AST_Label
@@ -378,6 +399,10 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
});
this.walk(tw);
to_mangle.forEach(function(def){ def.mangle(options) });
if (options.cache) {
options.cache.cname = this.cname;
}
});
AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options){

View File

@@ -106,10 +106,12 @@ function defaults(args, defs, croak) {
};
function merge(obj, ext) {
var count = 0;
for (var i in ext) if (ext.hasOwnProperty(i)) {
obj[i] = ext[i];
count++;
}
return obj;
return count;
};
function noop() {};
@@ -298,5 +300,11 @@ Dictionary.prototype = {
for (var i in this._values)
ret.push(f(this._values[i], i.substr(1)));
return ret;
}
},
toObject: function() { return this._values }
};
Dictionary.fromObject = function(obj) {
var dict = new Dictionary();
dict._size = merge(dict._values, obj);
return dict;
};

View File

@@ -1,37 +1,51 @@
{
"name": "uglify-js",
"description": "JavaScript parser, mangler/compressor and beautifier toolkit",
"homepage": "http://lisperator.net/uglifyjs",
"main": "tools/node.js",
"version": "2.4.16",
"engines": { "node" : ">=0.4.0" },
"maintainers": [{
"name": "Mihai Bazon",
"email": "mihai.bazon@gmail.com",
"web": "http://lisperator.net/"
}],
"repository": {
"type": "git",
"url": "https://github.com/mishoo/UglifyJS2.git"
},
"dependencies": {
"async" : "~0.2.6",
"source-map" : "0.1.34",
"yargs": "~1.3.3",
"uglify-to-browserify": "~1.0.0"
},
"devDependencies": {
"acorn": "~0.6.0",
"escodegen": "~1.3.3",
"esfuzz": "~0.3.1",
"estraverse": "~1.5.1"
},
"browserify": {
"transform": [ "uglify-to-browserify" ]
},
"bin": {
"uglifyjs" : "bin/uglifyjs"
},
"license": "BSD",
"scripts": {"test": "node test/run-tests.js"}
"homepage": "http://lisperator.net/uglifyjs",
"author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
"license": "BSD",
"version": "2.4.23",
"engines": {
"node": ">=0.4.0"
},
"maintainers": [
"Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)"
],
"repository": {
"type": "git",
"url": "https://github.com/mishoo/UglifyJS2.git"
},
"bugs": {
"url": "https://github.com/mishoo/UglifyJS2/issues"
},
"main": "tools/node.js",
"bin": {
"uglifyjs": "bin/uglifyjs"
},
"files": [
"bin",
"lib",
"tools",
"LICENSE"
],
"dependencies": {
"async": "~0.2.6",
"source-map": "0.1.34",
"uglify-to-browserify": "~1.0.0",
"yargs": "~3.5.4"
},
"devDependencies": {
"acorn": "~0.6.0",
"escodegen": "~1.3.3",
"esfuzz": "~0.3.1",
"estraverse": "~1.5.1"
},
"browserify": {
"transform": [
"uglify-to-browserify"
]
},
"scripts": {
"test": "node test/run-tests.js"
}
}

View File

@@ -86,7 +86,9 @@ ifs_4: {
x(foo)[10].bar.baz = something_else();
}
expect: {
x(foo)[10].bar.baz = (foo && bar) ? something() : something_else();
foo && bar
? x(foo)[10].bar.baz = something()
: x(foo)[10].bar.baz = something_else();
}
}
@@ -133,6 +135,7 @@ ifs_6: {
comparisons: true
};
input: {
var x;
if (!foo && !bar && !baz && !boo) {
x = 10;
} else {
@@ -140,6 +143,7 @@ ifs_6: {
}
}
expect: {
var x;
x = foo || bar || baz || boo ? 20 : 10;
}
}
@@ -149,6 +153,7 @@ cond_1: {
conditionals: true
};
input: {
var do_something; // if undeclared it's assumed to have side-effects
if (some_condition()) {
do_something(x);
} else {
@@ -156,6 +161,7 @@ cond_1: {
}
}
expect: {
var do_something;
do_something(some_condition() ? x : y);
}
}
@@ -165,6 +171,7 @@ cond_2: {
conditionals: true
};
input: {
var x, FooBar;
if (some_condition()) {
x = new FooBar(1);
} else {
@@ -172,6 +179,7 @@ cond_2: {
}
}
expect: {
var x, FooBar;
x = new FooBar(some_condition() ? 1 : 2);
}
}
@@ -181,6 +189,7 @@ cond_3: {
conditionals: true
};
input: {
var FooBar;
if (some_condition()) {
new FooBar(1);
} else {
@@ -188,6 +197,7 @@ cond_3: {
}
}
expect: {
var FooBar;
some_condition() ? new FooBar(1) : FooBar(2);
}
}
@@ -197,6 +207,7 @@ cond_4: {
conditionals: true
};
input: {
var do_something;
if (some_condition()) {
do_something();
} else {
@@ -204,6 +215,7 @@ cond_4: {
}
}
expect: {
var do_something;
some_condition(), do_something();
}
}
@@ -303,6 +315,7 @@ cond_7_1: {
evaluate : true
};
input: {
var x;
// access to global should be assumed to have side effects
if (y) {
x = 1+1;
@@ -311,6 +324,7 @@ cond_7_1: {
}
}
expect: {
var x;
x = (y, 2);
}
}
@@ -321,6 +335,7 @@ cond_8: {
evaluate : true
};
input: {
var a;
// compress these
a = condition ? true : false;
@@ -355,6 +370,7 @@ cond_8: {
}
expect: {
var a;
a = !!condition;
a = !condition;
a = !!condition();
@@ -367,4 +383,4 @@ cond_8: {
a = condition ? 0 : true;
a = condition ? 1 : 0;
}
}
}

View File

@@ -1,5 +1,5 @@
unused_funarg_1: {
options = { unused: true };
options = { unused: true, unsafe: true };
input: {
function f(a, b, c, d, e) {
return a + b;
@@ -13,7 +13,7 @@ unused_funarg_1: {
}
unused_funarg_2: {
options = { unused: true };
options = { unused: true, unsafe: true };
input: {
function f(a, b, c, d, e) {
return a + c;
@@ -165,7 +165,7 @@ used_var_in_catch: {
}
keep_fnames: {
options = { unused: true, keep_fnames: true };
options = { unused: true, keep_fnames: true, unsafe: true };
input: {
function foo() {
return function bar(baz) {};
@@ -176,4 +176,4 @@ keep_fnames: {
return function bar() {};
}
}
}
}

View File

@@ -6,7 +6,7 @@ NaN_and_Infinity_must_have_parens: {
}
expect: {
(1/0).toString();
(0/0).toString();
NaN.toString(); // transformation to 0/0 dropped
}
}

View File

@@ -84,7 +84,12 @@ function run_compress_tests() {
warnings: false
});
var cmp = new U.Compressor(options, true);
var expect = make_code(as_toplevel(test.expect), false);
var expect;
if (test.expect) {
expect = make_code(as_toplevel(test.expect), false);
} else {
expect = test.expect_exact;
}
var input = as_toplevel(test.input);
var input_code = make_code(test.input);
var output = input.transform(cmp);
@@ -150,7 +155,7 @@ function parse_test(file) {
}
if (node instanceof U.AST_LabeledStatement) {
assert.ok(
node.label.name == "input" || node.label.name == "expect",
node.label.name == "input" || node.label.name == "expect" || node.label.name == "expect_exact",
tmpl("Unsupported label {name} [{line},{col}]", {
name: node.label.name,
line: node.label.start.line,
@@ -162,7 +167,16 @@ function parse_test(file) {
if (stat.body.length == 1) stat = stat.body[0];
else if (stat.body.length == 0) stat = new U.AST_EmptyStatement();
}
test[node.label.name] = stat;
if (node.label.name === "expect_exact") {
if (!(stat.TYPE === "SimpleStatement" && stat.body.TYPE === "String")) {
throw new Error(
"The value of the expect_exact clause should be a string, " +
"like `expect_exact: \"some.exact.javascript;\"`");
}
test[node.label.name] = stat.body.start.value
} else {
test[node.label.name] = stat;
}
return true;
}
});

5603
tools/domprops.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,8 @@
var path = require("path");
var fs = require("fs");
var vm = require("vm");
var sys = require("util");
var UglifyJS = vm.createContext({
sys : sys,
console : console,
process : process,
Buffer : Buffer,
@@ -19,7 +17,7 @@ function load_global(file) {
} catch(ex) {
// XXX: in case of a syntax error, the message is kinda
// useless. (no location information).
sys.debug("ERROR in file: " + file + " / " + ex);
console.log("ERROR in file: " + file + " / " + ex);
process.exit(1);
}
};
@@ -33,7 +31,8 @@ var FILES = exports.FILES = [
"../lib/output.js",
"../lib/compress.js",
"../lib/sourcemap.js",
"../lib/mozilla-ast.js"
"../lib/mozilla-ast.js",
"../lib/propmangle.js"
].map(function(file){
return fs.realpathSync(path.join(path.dirname(__filename), file));
});
@@ -41,7 +40,7 @@ var FILES = exports.FILES = [
FILES.forEach(load_global);
UglifyJS.AST_Node.warn_function = function(txt) {
sys.error("WARN: " + txt);
console.error("WARN: %s", txt);
};
// XXX: perhaps we shouldn't export everything but heck, I'm lazy.
@@ -192,3 +191,63 @@ exports.describe_ast = function() {
doitem(UglifyJS.AST_Node);
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;
}
exports.readReservedFile = readReservedFile;
exports.readDefaultReservedFile = function(reserved) {
return readReservedFile(path.join(__dirname, "domprops.json"), reserved);
};
exports.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;
};
exports.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");
}
};

61
tools/props.html Normal file
View File

@@ -0,0 +1,61 @@
<html>
<head>
</head>
<body>
<script>(function(){
var props = {};
function addObject(obj) {
if (obj == null) return;
try {
Object.getOwnPropertyNames(obj).forEach(add);
} catch(ex) {}
if (obj.prototype) {
Object.getOwnPropertyNames(obj.prototype).forEach(add);
}
if (typeof obj == "function") {
try {
Object.getOwnPropertyNames(new obj).forEach(add);
} catch(ex) {}
}
}
function add(name) {
props[name] = true;
}
Object.getOwnPropertyNames(window).forEach(function(thing){
addObject(window[thing]);
});
try {
addObject(new Event("click"));
addObject(new Event("contextmenu"));
addObject(new Event("mouseup"));
addObject(new Event("mousedown"));
addObject(new Event("keydown"));
addObject(new Event("keypress"));
addObject(new Event("keyup"));
} catch(ex) {}
var ta = document.createElement("textarea");
ta.style.width = "100%";
ta.style.height = "20em";
ta.style.boxSizing = "border-box";
<!-- ta.value = Object.keys(props).sort(cmp).map(function(name){ -->
<!-- return JSON.stringify(name); -->
<!-- }).join(",\n"); -->
ta.value = JSON.stringify({
vars: [],
props: Object.keys(props).sort(cmp)
}, null, 2);
document.body.appendChild(ta);
function cmp(a, b) {
a = a.toLowerCase();
b = b.toLowerCase();
return a < b ? -1 : a > b ? 1 : 0;
}
})();</script>
</body>
</html>