Compare commits

...

27 Commits

Author SHA1 Message Date
Alex Lam S.L
4e12a6f740 v3.0.25 2017-07-16 11:05:53 +08:00
Alex Lam S.L
b35dfc2599 reject malformed CLI parameters (#2239)
fixes #2237
2017-07-15 23:50:27 +08:00
Alex Lam S.L
9e1da9235e ensure ie8 works with mangled properties (#2238)
fixes #2234
2017-07-15 22:50:59 +08:00
Alex Lam S.L
a5ffe2c23f drop unused builtin globals under unsafe (#2236)
fixes #2233
2017-07-15 15:16:11 +08:00
Alex Lam S.L
9282e7b0c6 fix unsafe evaluate of Object static methods (#2232)
fixes #2231
2017-07-14 19:52:01 +08:00
Alex Lam S.L
5229cb2b1b drop unused compound assignments (#2230)
fixes #2226
2017-07-14 00:39:34 +08:00
Alex Lam S.L
458e3e15f0 enhance passes (#2229)
- remove hardcoded upper limit
- continue based on node count reduction
- emit verbose statistics

fixes #2226
2017-07-13 02:18:59 +08:00
Alex Lam S.L
c615a1e80a fix gzip stream in test/benchmark.js (#2228) 2017-07-12 02:55:57 +08:00
Alex Lam S.L
10a938cb79 enhance source mapping on IIFEs (#2224)
fixes #2213
2017-07-11 02:34:28 +08:00
Alex Lam S.L
4956ad311b benchmark gzipped output (#2220) 2017-07-09 01:44:59 +08:00
kzc
145874e504 docs: update benchmarks using node 8, add babili (#2218) 2017-07-09 01:06:15 +08:00
Alex Lam S.L
bd7be07c38 v3.0.24 2017-07-08 12:53:20 +08:00
Alex Lam S.L
71ee91e716 handle duplicate argument names in collapse_vars (#2215) 2017-07-08 04:42:35 +08:00
Alex Lam S.L
4f70d2e28c inlining of static methods & constants (#2211)
- guard by `unsafe`
- support `Array`, `Math`, `Number`, `Object` & `String`

fixes #2207
2017-07-07 05:35:32 +08:00
Alex Lam S.L
4b6ca5e742 inline property access of object literal (#2209)
- only if property value is side-effect-free
- guard by `unsafe`

fixes #2208
2017-07-06 21:51:58 +08:00
Alex Lam S.L
9306da3c58 suppress collapse_vars of this as call argument (#2204)
fixes #2203
2017-07-06 01:03:52 +08:00
Alex Lam S.L
1ac25fc032 improve compress granularity through typeofs (#2201)
fixes #2198
2017-07-05 19:20:33 +08:00
Alex Lam S.L
5f046c724b minor clean-ups to evaluate (#2197) 2017-07-03 18:52:39 +08:00
Alex Lam S.L
af0262b7e5 improve parenthesis emission (#2196)
- eliminate `throw` usages
- suppress extraneous parenthesis
  - `new function() {foo.bar()}.baz`
  - `for (function() { "foo" in bar; };;);`
2017-07-03 04:17:37 +08:00
Alex Lam S.L
6b3aeff1d8 clean up TreeWalker.pop() (#2195)
Remove superfluous parameter.
2017-07-03 03:23:38 +08:00
Alex Lam S.L
20e4f8277f refactor throw usage within compress (#2193)
Eliminate exceptional constructs from normal control flow.
2017-07-03 02:10:56 +08:00
kzc
f3a487a368 document fast mangle-only minify mode (#2194) 2017-07-03 01:37:04 +08:00
Alex Lam S.L
2dde41615a v3.0.23 2017-07-02 17:24:22 +08:00
Alex Lam S.L
8b69a3d18e drop argument value after collapse_vars (#2190) 2017-07-02 04:28:11 +08:00
Alex Lam S.L
d40950b741 improve inline efficiency (#2188)
... by teaching `collapse_vars` some new tricks.

fixes #2187
2017-07-02 01:05:14 +08:00
Alex Lam S.L
7659ea1d2e v3.0.22 2017-06-30 11:18:34 +08:00
Alex Lam S.L
bdeadffbf5 improve usability of name cache under minify() (#2176)
fixes #2174
2017-06-29 12:48:34 +08:00
29 changed files with 1406 additions and 436 deletions

View File

@@ -111,7 +111,7 @@ a double dash to prevent input files being used as option arguments:
By default UglifyJS will not try to be IE-proof. By default UglifyJS will not try to be IE-proof.
--keep-fnames Do not mangle/drop function names. Useful for --keep-fnames Do not mangle/drop function names. Useful for
code relying on Function.prototype.name. code relying on Function.prototype.name.
--name-cache File to hold mangled name mappings. --name-cache <file> File to hold mangled name mappings.
--self Build UglifyJS as a library (implies --wrap UglifyJS) --self Build UglifyJS as a library (implies --wrap UglifyJS)
--source-map [options] Enable source map/specify source map options: --source-map [options] Enable source map/specify source map options:
`base` Path to compute relative paths from input files. `base` Path to compute relative paths from input files.
@@ -383,7 +383,47 @@ var code = {
var options = { toplevel: true }; var options = { toplevel: true };
var result = UglifyJS.minify(code, options); var result = UglifyJS.minify(code, options);
console.log(result.code); console.log(result.code);
// console.log(function(n,o){return n+o}(3,7)); // console.log(3+7);
```
The `nameCache` option:
```javascript
var options = {
mangle: {
toplevel: true,
},
nameCache: {}
};
var result1 = UglifyJS.minify({
"file1.js": "function add(first, second) { return first + second; }"
}, options);
var result2 = UglifyJS.minify({
"file2.js": "console.log(add(1 + 2, 3 + 4));"
}, options);
console.log(result1.code);
// function n(n,r){return n+r}
console.log(result2.code);
// console.log(n(3,7));
```
You may persist the name cache to the file system in the following way:
```javascript
var cacheFileName = "/tmp/cache.json";
var options = {
mangle: {
properties: true,
},
nameCache: JSON.parse(fs.readFileSync(cacheFileName, "utf8"))
};
fs.writeFileSync("part1.js", UglifyJS.minify({
"file1.js": fs.readFileSync("file1.js", "utf8"),
"file2.js": fs.readFileSync("file2.js", "utf8")
}, options).code, "utf8");
fs.writeFileSync("part2.js", UglifyJS.minify({
"file3.js": fs.readFileSync("file3.js", "utf8"),
"file4.js": fs.readFileSync("file4.js", "utf8")
}, options).code, "utf8");
fs.writeFileSync(cacheFileName, JSON.stringify(options.nameCache), "utf8");
``` ```
An example of a combination of `minify()` options: An example of a combination of `minify()` options:
@@ -461,6 +501,13 @@ if (result.error) throw result.error;
- `toplevel` (default `false`) - set to `true` if you wish to enable top level - `toplevel` (default `false`) - set to `true` if you wish to enable top level
variable and function name mangling and to drop unused variables and functions. variable and function name mangling and to drop unused variables and functions.
- `nameCache` (default `null`) - pass an empty object `{}` or a previously
used `nameCache` object if you wish to cache mangled variable and
property names across multiple invocations of `minify()`. Note: this is
a read/write property. `minify()` will read the name cache state of this
object and update it during minification so that it may be
reused or externally persisted by the user.
- `ie8` (default `false`) - set to `true` to support IE8. - `ie8` (default `false`) - set to `true` to support IE8.
## Minify options structure ## Minify options structure
@@ -487,6 +534,7 @@ if (result.error) throw result.error;
sourceMap: { sourceMap: {
// source map options // source map options
}, },
nameCache: null, // or specify a name cache object
toplevel: false, toplevel: false,
ie8: false, ie8: false,
} }
@@ -596,6 +644,10 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u
- `booleans` -- various optimizations for boolean context, for example `!!a - `booleans` -- various optimizations for boolean context, for example `!!a
? b : c → a ? b : c` ? b : c → a ? b : c`
- `typeofs` -- default `true`. Transforms `typeof foo == "undefined"` into
`foo === void 0`. Note: recommend to set this value to `false` for IE10 and
earlier versions due to known issues.
- `loops` -- optimizations for `do`, `while` and `for` loops when we can - `loops` -- optimizations for `do`, `while` and `for` loops when we can
statically determine the condition statically determine the condition
@@ -669,7 +721,7 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u
compressor from discarding function names. Useful for code relying on compressor from discarding function names. Useful for code relying on
`Function.prototype.name`. See also: the `keep_fnames` [mangle option](#mangle). `Function.prototype.name`. See also: the `keep_fnames` [mangle option](#mangle).
- `passes` -- default `1`. Number of times to run compress with a maximum of 3. - `passes` -- default `1`. The maximum number of times to run compress.
In some cases more than one pass leads to further compressed code. Keep in In some cases more than one pass leads to further compressed code. Keep in
mind more passes will take more time. mind more passes will take more time.
@@ -825,7 +877,6 @@ when this flag is on:
- `new Object()` → `{}` - `new Object()` → `{}`
- `String(exp)` or `exp.toString()` → `"" + exp` - `String(exp)` or `exp.toString()` → `"" + exp`
- `new Object/RegExp/Function/Error/Array (...)` → we discard the `new` - `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 - `void 0` → `undefined` (if there is a variable named "undefined" in
scope; we do it because the variable name will be mangled, typically scope; we do it because the variable name will be mangled, typically
reduced to a single character) reduced to a single character)
@@ -978,3 +1029,30 @@ in total it's a bit more than just using UglifyJS's own parser.
[acorn]: https://github.com/ternjs/acorn [acorn]: https://github.com/ternjs/acorn
[sm-spec]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k [sm-spec]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k
### Uglify Fast Minify Mode
It's not well known, but whitespace removal and symbol mangling accounts
for 95% of the size reduction in minified code for most javascript - not
elaborate code transforms. One can simply disable `compress` to speed up
Uglify builds by 3 to 4 times. In this fast `mangle`-only mode Uglify has
comparable minify speeds and gzip sizes to
[`butternut`](https://www.npmjs.com/package/butternut):
| d3.js | minify size | gzip size | minify time (seconds) |
| --- | ---: | ---: | ---: |
| original | 451,131 | 108,733 | - |
| uglify-js@3.0.24 mangle=false, compress=false | 316,600 | 85,245 | 0.70 |
| uglify-js@3.0.24 mangle=true, compress=false | 220,216 | 72,730 | 1.13 |
| butternut@0.4.6 | 217,568 | 72,738 | 1.41 |
| uglify-js@3.0.24 mangle=true, compress=true | 212,511 | 71,560 | 3.36 |
| babili@0.1.4 | 210,713 | 72,140 | 12.64 |
To enable fast minify mode from the CLI use:
```
uglifyjs file.js -m
```
To enable fast minify mode with the API use:
```js
UglifyJS.minify(code, { compress: false, mangle: true });
```

View File

@@ -35,11 +35,11 @@ else if (process.argv.indexOf("options") >= 0) program.helpInformation = functio
} }
return text.join("\n"); return text.join("\n");
}; };
program.option("-p, --parse <options>", "Specify parser options.", parse_js("parse", true)); program.option("-p, --parse <options>", "Specify parser options.", parse_js());
program.option("-c, --compress [options]", "Enable compressor/specify compressor options.", parse_js("compress", true)); program.option("-c, --compress [options]", "Enable compressor/specify compressor options.", parse_js());
program.option("-m, --mangle [options]", "Mangle names/specify mangler options.", parse_js("mangle", true)); program.option("-m, --mangle [options]", "Mangle names/specify mangler options.", parse_js());
program.option("--mangle-props [options]", "Mangle properties/specify mangler options.", parse_js("mangle-props", true)); program.option("--mangle-props [options]", "Mangle properties/specify mangler options.", parse_js());
program.option("-b, --beautify [options]", "Beautify output/specify output options.", parse_js("beautify", true)); program.option("-b, --beautify [options]", "Beautify output/specify output options.", parse_js());
program.option("-o, --output <file>", "Output file (default STDOUT)."); program.option("-o, --output <file>", "Output file (default STDOUT).");
program.option("--comments [filter]", "Preserve copyright comments in the output."); program.option("--comments [filter]", "Preserve copyright comments in the output.");
program.option("--config-file <file>", "Read minify() options from JSON file."); program.option("--config-file <file>", "Read minify() options from JSON file.");
@@ -106,17 +106,8 @@ if (program.mangleProps) {
if (typeof options.mangle != "object") options.mangle = {}; if (typeof options.mangle != "object") options.mangle = {};
options.mangle.properties = program.mangleProps; options.mangle.properties = program.mangleProps;
} }
var cache;
if (program.nameCache) { if (program.nameCache) {
cache = JSON.parse(read_file(program.nameCache, "{}")); options.nameCache = 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") { if (program.output == "ast") {
options.output = { options.output = {
@@ -266,9 +257,7 @@ function run() {
print(result.code); print(result.code);
} }
if (program.nameCache) { if (program.nameCache) {
fs.writeFileSync(program.nameCache, JSON.stringify(cache, function(key, value) { fs.writeFileSync(program.nameCache, JSON.stringify(options.nameCache));
return value instanceof UglifyJS.Dictionary ? value.toObject() : value;
}));
} }
if (result.timings) for (var phase in result.timings) { if (result.timings) for (var phase in result.timings) {
print_error("- " + phase + ": " + result.timings[phase].toFixed(3) + "s"); print_error("- " + phase + ": " + result.timings[phase].toFixed(3) + "s");
@@ -321,7 +310,7 @@ function read_file(path, default_value) {
} }
} }
function parse_js(flag, constants) { function parse_js(flag) {
return function(value, options) { return function(value, options) {
options = options || {}; options = options || {};
try { try {
@@ -339,7 +328,7 @@ function parse_js(flag, constants) {
if (node instanceof UglifyJS.AST_Assign) { if (node instanceof UglifyJS.AST_Assign) {
var name = node.left.print_to_string(); var name = node.left.print_to_string();
var value = node.right; var value = node.right;
if (!constants) { if (flag) {
options[name] = value; options[name] = value;
} else if (value instanceof UglifyJS.AST_Array) { } else if (value instanceof UglifyJS.AST_Array) {
options[name] = value.elements.map(to_string); options[name] = value.elements.map(to_string);
@@ -362,14 +351,18 @@ function parse_js(flag, constants) {
} }
})); }));
} catch(ex) { } catch(ex) {
if (flag) {
fatal("Error parsing arguments for '" + flag + "': " + value);
} else {
options[value] = null; options[value] = null;
} }
}
return options; return options;
} }
} }
function parse_source_map() { function parse_source_map() {
var parse = parse_js("sourceMap", true); var parse = parse_js();
return function(value, options) { return function(value, options) {
var hasContent = options && "content" in options; var hasContent = options && "content" in options;
var settings = parse(value, options); var settings = parse(value, options);
@@ -381,18 +374,6 @@ function parse_source_map() {
} }
} }
function to_cache(key) {
if (cache[key]) {
cache[key].props = UglifyJS.Dictionary.fromObject(cache[key].props);
} else {
cache[key] = {
cname: -1,
props: new UglifyJS.Dictionary()
};
}
return cache[key];
}
function skip_key(key) { function skip_key(key) {
return skip_keys.indexOf(key) >= 0; return skip_keys.indexOf(key) >= 0;
} }

View File

@@ -859,7 +859,7 @@ TreeWalker.prototype = {
if (!ret && descend) { if (!ret && descend) {
descend.call(node); descend.call(node);
} }
this.pop(node); this.pop();
return ret; return ret;
}, },
parent: function(n) { parent: function(n) {
@@ -873,9 +873,8 @@ TreeWalker.prototype = {
} }
this.stack.push(node); this.stack.push(node);
}, },
pop: function(node) { pop: function() {
this.stack.pop(); if (this.stack.pop() instanceof AST_Lambda) {
if (node instanceof AST_Lambda) {
this.directives = Object.getPrototypeOf(this.directives); this.directives = Object.getPrototypeOf(this.directives);
} }
}, },

View File

@@ -80,6 +80,7 @@ function Compressor(options, false_by_default) {
switches : !false_by_default, switches : !false_by_default,
top_retain : null, top_retain : null,
toplevel : !!(options && options["top_retain"]), toplevel : !!(options && options["top_retain"]),
typeofs : !false_by_default,
unsafe : false, unsafe : false,
unsafe_comps : false, unsafe_comps : false,
unsafe_Func : false, unsafe_Func : false,
@@ -147,10 +148,20 @@ merge(Compressor.prototype, {
node.process_expression(true); node.process_expression(true);
} }
var passes = +this.options.passes || 1; var passes = +this.options.passes || 1;
for (var pass = 0; pass < passes && pass < 3; ++pass) { var last_count = 1 / 0;
for (var pass = 0; pass < passes; pass++) {
if (pass > 0 || this.option("reduce_vars")) if (pass > 0 || this.option("reduce_vars"))
node.reset_opt_flags(this, true); node.reset_opt_flags(this, true);
node = node.transform(this); node = node.transform(this);
if (passes > 1) {
var count = 0;
node.walk(new TreeWalker(function() {
count++;
}));
this.info("pass " + pass + ": last_count: " + last_count + ", count: " + count);
if (count >= last_count) break;
last_count = count;
}
} }
if (this.option("expression")) { if (this.option("expression")) {
node.process_expression(false); node.process_expression(false);
@@ -678,6 +689,16 @@ merge(Compressor.prototype, {
return false; return false;
} }
function is_undeclared_ref(node) {
return node instanceof AST_SymbolRef && node.definition().undeclared;
}
var global_names = makePredicate("Array Boolean console Error Function Math Number RegExp Object String");
AST_SymbolRef.DEFMETHOD("is_declared", function(compressor) {
return !this.definition().undeclared
|| compressor.option("unsafe") && global_names(this.name);
});
function tighten_body(statements, compressor) { function tighten_body(statements, compressor) {
var CHANGED, max_iter = 10; var CHANGED, max_iter = 10;
do { do {
@@ -714,15 +735,23 @@ merge(Compressor.prototype, {
var candidates = []; var candidates = [];
var stat_index = statements.length; var stat_index = statements.length;
while (--stat_index >= 0) { while (--stat_index >= 0) {
// Treat parameters as collapsible in IIFE, i.e.
// function(a, b){ ... }(x());
// would be translated into equivalent assignments:
// var a = x(), b = undefined;
if (stat_index == 0 && compressor.option("unused")) extract_args();
// Find collapsible assignments
extract_candidates(statements[stat_index]); extract_candidates(statements[stat_index]);
while (candidates.length > 0) { while (candidates.length > 0) {
var candidate = candidates.pop(); var candidate = candidates.pop();
var lhs = get_lhs(candidate); var lhs = get_lhs(candidate);
if (!lhs || is_lhs_read_only(lhs)) continue; if (!lhs || is_lhs_read_only(lhs)) continue;
// Locate symbols which may execute code outside of scanning range
var lvalues = get_lvalues(candidate); var lvalues = get_lvalues(candidate);
if (lhs instanceof AST_SymbolRef) lvalues[lhs.name] = false; if (lhs instanceof AST_SymbolRef) lvalues[lhs.name] = false;
var side_effects = value_has_side_effects(candidate); var side_effects = value_has_side_effects(candidate);
var hit = false, abort = false, replaced = false; var hit = candidate.name instanceof AST_SymbolFunarg;
var abort = false, replaced = false;
var tt = new TreeTransformer(function(node, descend) { var tt = new TreeTransformer(function(node, descend) {
if (abort) return node; if (abort) return node;
// Skip nodes before `candidate` as quickly as possible // Skip nodes before `candidate` as quickly as possible
@@ -739,7 +768,7 @@ merge(Compressor.prototype, {
|| node instanceof AST_Call && lhs instanceof AST_PropAccess && lhs.equivalent_to(node.expression) || node instanceof AST_Call && lhs instanceof AST_PropAccess && lhs.equivalent_to(node.expression)
|| node instanceof AST_Debugger || node instanceof AST_Debugger
|| node instanceof AST_IterationStatement && !(node instanceof AST_For) || node instanceof AST_IterationStatement && !(node instanceof AST_For)
|| node instanceof AST_SymbolRef && node.undeclared() || node instanceof AST_SymbolRef && !node.is_declared(compressor)
|| node instanceof AST_Try || node instanceof AST_Try
|| node instanceof AST_With || node instanceof AST_With
|| parent instanceof AST_For && node !== parent.init) { || parent instanceof AST_For && node !== parent.init) {
@@ -771,6 +800,7 @@ merge(Compressor.prototype, {
right: candidate.value right: candidate.value
}); });
} }
candidate.write_only = false;
return candidate; return candidate;
} }
// These node types have child nodes that execute sequentially, // These node types have child nodes that execute sequentially,
@@ -803,6 +833,46 @@ merge(Compressor.prototype, {
} }
} }
function extract_args() {
var iife, fn = compressor.self();
if (fn instanceof AST_Function
&& !fn.name
&& !fn.uses_arguments
&& !fn.uses_eval
&& (iife = compressor.parent()) instanceof AST_Call
&& iife.expression === fn) {
var names = Object.create(null);
for (var i = fn.argnames.length; --i >= 0;) {
var sym = fn.argnames[i];
if (sym.name in names) continue;
names[sym.name] = true;
var arg = iife.args[i];
if (!arg) arg = make_node(AST_Undefined, sym);
else {
var tw = new TreeWalker(function(node) {
if (!arg) return true;
if (node instanceof AST_SymbolRef && fn.variables.has(node.name)) {
var s = node.definition().scope;
if (s !== scope) while (s = s.parent_scope) {
if (s === scope) return true;
}
arg = null;
}
if (node instanceof AST_This && !tw.find_parent(AST_Scope)) {
arg = null;
return true;
}
});
arg.walk(tw);
}
if (arg) candidates.unshift(make_node(AST_VarDef, sym, {
name: sym,
value: arg
}));
}
}
}
function extract_candidates(expr) { function extract_candidates(expr) {
if (expr instanceof AST_Assign && !expr.left.has_side_effects(compressor) if (expr instanceof AST_Assign && !expr.left.has_side_effects(compressor)
|| expr instanceof AST_Unary && (expr.operator == "++" || expr.operator == "--")) { || expr instanceof AST_Unary && (expr.operator == "++" || expr.operator == "--")) {
@@ -823,7 +893,7 @@ merge(Compressor.prototype, {
function get_lhs(expr) { function get_lhs(expr) {
if (expr instanceof AST_VarDef) { if (expr instanceof AST_VarDef) {
var def = expr.name.definition(); var def = expr.name.definition();
if (def.orig.length > 1 if (def.orig.length > 1 && !(expr.name instanceof AST_SymbolFunarg)
|| def.references.length == 1 && !compressor.exposed(def)) { || def.references.length == 1 && !compressor.exposed(def)) {
return make_node(AST_SymbolRef, expr.name, expr.name); return make_node(AST_SymbolRef, expr.name, expr.name);
} }
@@ -865,6 +935,14 @@ merge(Compressor.prototype, {
} }
function remove_candidate(expr) { function remove_candidate(expr) {
if (expr.name instanceof AST_SymbolFunarg) {
var index = compressor.self().argnames.indexOf(expr.name);
var args = compressor.parent().args;
if (args[index]) args[index] = make_node(AST_Number, args[index], {
value: 0
});
return true;
}
var found = false; var found = false;
return statements[stat_index].transform(new TreeTransformer(function(node, descend, in_list) { return statements[stat_index].transform(new TreeTransformer(function(node, descend, in_list) {
if (found) return node; if (found) return node;
@@ -1153,21 +1231,21 @@ merge(Compressor.prototype, {
for (var i = 0, len = statements.length; i < len; i++) { for (var i = 0, len = statements.length; i < len; i++) {
var stat = statements[i]; var stat = statements[i];
if (prev) { if (prev) {
if (stat instanceof AST_For) { if (stat instanceof AST_For && !(stat.init instanceof AST_Definitions)) {
try { var abort = false;
prev.body.walk(new TreeWalker(function(node){ prev.body.walk(new TreeWalker(function(node) {
if (node instanceof AST_Binary && node.operator == "in") if (abort || node instanceof AST_Scope) return true;
throw cons_seq; if (node instanceof AST_Binary && node.operator == "in") {
})); abort = true;
if (stat.init && !(stat.init instanceof AST_Definitions)) { return true;
stat.init = cons_seq(stat.init);
} }
else if (!stat.init) { }));
if (!abort) {
if (stat.init) stat.init = cons_seq(stat.init);
else {
stat.init = prev.body.drop_side_effect_free(compressor); stat.init = prev.body.drop_side_effect_free(compressor);
n--; n--;
} }
} catch(ex) {
if (ex !== cons_seq) throw ex;
} }
} }
else if (stat instanceof AST_If) { else if (stat instanceof AST_If) {
@@ -1252,12 +1330,12 @@ merge(Compressor.prototype, {
// returns true if this node may be null, undefined or contain `AST_Accessor` // returns true if this node may be null, undefined or contain `AST_Accessor`
(function(def) { (function(def) {
AST_Node.DEFMETHOD("may_throw_on_access", function(compressor) { AST_Node.DEFMETHOD("may_throw_on_access", function(compressor) {
var pure_getters = compressor.option("pure_getters"); return !compressor.option("pure_getters")
return !pure_getters || this._throw_on_access(pure_getters); || this._dot_throw(compressor);
}); });
function is_strict(pure_getters) { function is_strict(compressor) {
return /strict/.test(pure_getters); return /strict/.test(compressor.option("pure_getters"));
} }
def(AST_Node, is_strict); def(AST_Node, is_strict);
@@ -1265,8 +1343,8 @@ merge(Compressor.prototype, {
def(AST_Undefined, return_true); def(AST_Undefined, return_true);
def(AST_Constant, return_false); def(AST_Constant, return_false);
def(AST_Array, return_false); def(AST_Array, return_false);
def(AST_Object, function(pure_getters) { def(AST_Object, function(compressor) {
if (!is_strict(pure_getters)) return false; if (!is_strict(compressor)) return false;
for (var i = this.properties.length; --i >=0;) for (var i = this.properties.length; --i >=0;)
if (this.properties[i].value instanceof AST_Accessor) return true; if (this.properties[i].value instanceof AST_Accessor) return true;
return false; return false;
@@ -1276,37 +1354,38 @@ merge(Compressor.prototype, {
def(AST_UnaryPrefix, function() { def(AST_UnaryPrefix, function() {
return this.operator == "void"; return this.operator == "void";
}); });
def(AST_Binary, function(pure_getters) { def(AST_Binary, function(compressor) {
switch (this.operator) { switch (this.operator) {
case "&&": case "&&":
return this.left._throw_on_access(pure_getters); return this.left._dot_throw(compressor);
case "||": case "||":
return this.left._throw_on_access(pure_getters) return this.left._dot_throw(compressor)
&& this.right._throw_on_access(pure_getters); && this.right._dot_throw(compressor);
default: default:
return false; return false;
} }
}) })
def(AST_Assign, function(pure_getters) { def(AST_Assign, function(compressor) {
return this.operator == "=" return this.operator == "="
&& this.right._throw_on_access(pure_getters); && this.right._dot_throw(compressor);
}) })
def(AST_Conditional, function(pure_getters) { def(AST_Conditional, function(compressor) {
return this.consequent._throw_on_access(pure_getters) return this.consequent._dot_throw(compressor)
|| this.alternative._throw_on_access(pure_getters); || this.alternative._dot_throw(compressor);
}) })
def(AST_Sequence, function(pure_getters) { def(AST_Sequence, function(compressor) {
return this.expressions[this.expressions.length - 1]._throw_on_access(pure_getters); return this.expressions[this.expressions.length - 1]._dot_throw(compressor);
}); });
def(AST_SymbolRef, function(pure_getters) { def(AST_SymbolRef, function(compressor) {
if (this.is_undefined) return true; if (this.is_undefined) return true;
if (!is_strict(pure_getters)) return false; if (!is_strict(compressor)) return false;
if (is_undeclared_ref(this) && this.is_declared(compressor)) return false;
if (this.is_immutable()) return false; if (this.is_immutable()) return false;
var fixed = this.fixed_value(); var fixed = this.fixed_value();
return !fixed || fixed._throw_on_access(pure_getters); return !fixed || fixed._dot_throw(compressor);
}); });
})(function(node, func) { })(function(node, func) {
node.DEFMETHOD("_throw_on_access", func); node.DEFMETHOD("_dot_throw", func);
}); });
/* -----[ boolean/negation helpers ]----- */ /* -----[ boolean/negation helpers ]----- */
@@ -1487,13 +1566,8 @@ merge(Compressor.prototype, {
// descendant of AST_Node. // descendant of AST_Node.
AST_Node.DEFMETHOD("evaluate", function(compressor){ AST_Node.DEFMETHOD("evaluate", function(compressor){
if (!compressor.option("evaluate")) return this; if (!compressor.option("evaluate")) return this;
try {
var val = this._eval(compressor); var val = this._eval(compressor);
return !val || val instanceof RegExp || typeof val != "object" ? val : this; return !val || val instanceof RegExp || typeof val != "object" ? val : this;
} catch(ex) {
if (ex !== def) throw ex;
return this;
}
}); });
var unaryPrefix = makePredicate("! ~ - + void"); var unaryPrefix = makePredicate("! ~ - + void");
AST_Node.DEFMETHOD("is_constant", function(){ AST_Node.DEFMETHOD("is_constant", function(){
@@ -1539,27 +1613,28 @@ merge(Compressor.prototype, {
def(AST_Statement, function(){ def(AST_Statement, function(){
throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start)); throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start));
}); });
def(AST_Lambda, function(){ def(AST_Lambda, return_this);
throw def;
});
function ev(node, compressor) { function ev(node, compressor) {
if (!compressor) throw new Error("Compressor must be passed"); if (!compressor) throw new Error("Compressor must be passed");
return node._eval(compressor); return node._eval(compressor);
}; };
def(AST_Node, function(){ def(AST_Node, return_this);
throw def; // not constant
});
def(AST_Constant, function(){ def(AST_Constant, function(){
return this.getValue(); return this.getValue();
}); });
def(AST_Array, function(compressor){ def(AST_Array, function(compressor){
if (compressor.option("unsafe")) { if (compressor.option("unsafe")) {
return this.elements.map(function(element) { var elements = [];
return ev(element, compressor); for (var i = 0, len = this.elements.length; i < len; i++) {
}); var element = this.elements[i];
var value = ev(element, compressor);
if (element === value) return this;
elements.push(value);
} }
throw def; return elements;
}
return this;
}); });
def(AST_Object, function(compressor){ def(AST_Object, function(compressor){
if (compressor.option("unsafe")) { if (compressor.option("unsafe")) {
@@ -1571,163 +1646,255 @@ merge(Compressor.prototype, {
key = key.name; key = key.name;
} else if (key instanceof AST_Node) { } else if (key instanceof AST_Node) {
key = ev(key, compressor); key = ev(key, compressor);
if (key === prop.key) return this;
} }
if (typeof Object.prototype[key] === 'function') { if (typeof Object.prototype[key] === 'function') {
throw def; return this;
} }
val[key] = ev(prop.value, compressor); val[key] = ev(prop.value, compressor);
if (val[key] === prop.value) return this;
} }
return val; return val;
} }
throw def; return this;
}); });
def(AST_UnaryPrefix, function(compressor){ def(AST_UnaryPrefix, function(compressor){
var e = this.expression;
switch (this.operator) {
case "!": return !ev(e, compressor);
case "typeof":
// Function would be evaluated to an array and so typeof would // Function would be evaluated to an array and so typeof would
// incorrectly return 'object'. Hence making is a special case. // incorrectly return 'object'. Hence making is a special case.
if (e instanceof AST_Function) return typeof function(){}; if (this.operator == "typeof" && this.expression instanceof AST_Function) {
return typeof function(){};
e = ev(e, compressor); }
var e = ev(this.expression, compressor);
if (e === this.expression) return this;
switch (this.operator) {
case "!": return !e;
case "typeof":
// typeof <RegExp> returns "object" or "function" on different platforms // typeof <RegExp> returns "object" or "function" on different platforms
// so cannot evaluate reliably // so cannot evaluate reliably
if (e instanceof RegExp) throw def; if (e instanceof RegExp) return this;
return typeof e; return typeof e;
case "void": return void ev(e, compressor); case "void": return void e;
case "~": return ~ev(e, compressor); case "~": return ~e;
case "-": return -ev(e, compressor); case "-": return -e;
case "+": return +ev(e, compressor); case "+": return +e;
} }
throw def; return this;
}); });
def(AST_Binary, function(c){ def(AST_Binary, function(compressor){
var left = this.left, right = this.right, result; var left = ev(this.left, compressor);
if (left === this.left) return this;
var right = ev(this.right, compressor);
if (right === this.right) return this;
var result;
switch (this.operator) { switch (this.operator) {
case "&&" : result = ev(left, c) && ev(right, c); break; case "&&" : result = left && right; break;
case "||" : result = ev(left, c) || ev(right, c); break; case "||" : result = left || right; break;
case "|" : result = ev(left, c) | ev(right, c); break; case "|" : result = left | right; break;
case "&" : result = ev(left, c) & ev(right, c); break; case "&" : result = left & right; break;
case "^" : result = ev(left, c) ^ ev(right, c); break; case "^" : result = left ^ right; break;
case "+" : result = ev(left, c) + ev(right, c); break; case "+" : result = left + right; break;
case "*" : result = ev(left, c) * ev(right, c); break; case "*" : result = left * right; break;
case "/" : result = ev(left, c) / ev(right, c); break; case "/" : result = left / right; break;
case "%" : result = ev(left, c) % ev(right, c); break; case "%" : result = left % right; break;
case "-" : result = ev(left, c) - ev(right, c); break; case "-" : result = left - right; break;
case "<<" : result = ev(left, c) << ev(right, c); break; case "<<" : result = left << right; break;
case ">>" : result = ev(left, c) >> ev(right, c); break; case ">>" : result = left >> right; break;
case ">>>" : result = ev(left, c) >>> ev(right, c); break; case ">>>" : result = left >>> right; break;
case "==" : result = ev(left, c) == ev(right, c); break; case "==" : result = left == right; break;
case "===" : result = ev(left, c) === ev(right, c); break; case "===" : result = left === right; break;
case "!=" : result = ev(left, c) != ev(right, c); break; case "!=" : result = left != right; break;
case "!==" : result = ev(left, c) !== ev(right, c); break; case "!==" : result = left !== right; break;
case "<" : result = ev(left, c) < ev(right, c); break; case "<" : result = left < right; break;
case "<=" : result = ev(left, c) <= ev(right, c); break; case "<=" : result = left <= right; break;
case ">" : result = ev(left, c) > ev(right, c); break; case ">" : result = left > right; break;
case ">=" : result = ev(left, c) >= ev(right, c); break; case ">=" : result = left >= right; break;
default: default:
throw def; return this;
} }
if (isNaN(result) && c.find_parent(AST_With)) { if (isNaN(result) && compressor.find_parent(AST_With)) {
// leave original expression as is // leave original expression as is
throw def; return this;
} }
return result; return result;
}); });
def(AST_Conditional, function(compressor){ def(AST_Conditional, function(compressor){
return ev(this.condition, compressor) var condition = ev(this.condition, compressor);
? ev(this.consequent, compressor) if (condition === this.condition) return this;
: ev(this.alternative, compressor); var node = condition ? this.consequent : this.alternative;
var value = ev(node, compressor);
return value === node ? this : value;
}); });
def(AST_SymbolRef, function(compressor){ def(AST_SymbolRef, function(compressor){
if (!compressor.option("reduce_vars") || this._evaluating) throw def; if (!compressor.option("reduce_vars")) return this;
this._evaluating = true;
try {
var fixed = this.fixed_value(); var fixed = this.fixed_value();
if (!fixed) throw def; if (!fixed) return this;
this._eval = return_this;
var value = ev(fixed, compressor); var value = ev(fixed, compressor);
if (value === fixed) {
delete this._eval;
return this;
}
if (!HOP(fixed, "_eval")) fixed._eval = function() { if (!HOP(fixed, "_eval")) fixed._eval = function() {
return value; return value;
}; };
if (value && typeof value == "object" && this.definition().escaped) throw def; if (value && typeof value == "object" && this.definition().escaped) {
return value; delete this._eval;
} finally { return this;
this._evaluating = false;
} }
this._eval = fixed._eval;
return value;
}); });
var global_objs = {
Array: Array,
Math: Math,
Number: Number,
String: String,
};
function convert_to_predicate(obj) {
for (var key in obj) {
obj[key] = makePredicate(obj[key]);
}
}
var static_values = {
Math: [
"E",
"LN10",
"LN2",
"LOG2E",
"LOG10E",
"PI",
"SQRT1_2",
"SQRT2",
],
Number: [
"MAX_VALUE",
"MIN_VALUE",
"NaN",
"NEGATIVE_INFINITY",
"POSITIVE_INFINITY",
],
};
convert_to_predicate(static_values);
def(AST_PropAccess, function(compressor){ def(AST_PropAccess, function(compressor){
if (compressor.option("unsafe")) { if (compressor.option("unsafe")) {
var key = this.property; var key = this.property;
if (key instanceof AST_Node) { if (key instanceof AST_Node) {
key = ev(key, compressor); key = ev(key, compressor);
if (key === this.property) return this;
}
var exp = this.expression;
var val;
if (is_undeclared_ref(exp)) {
if (!(static_values[exp.name] || return_false)(key)) return this;
val = global_objs[exp.name];
} else {
val = ev(exp, compressor);
if (!val || val === exp || !HOP(val, key)) return this;
} }
var val = ev(this.expression, compressor);
if (val && HOP(val, key)) {
return val[key]; return val[key];
} }
} return this;
throw def;
}); });
var object_fns = [ var object_fns = [
'constructor', "constructor",
'toString', "toString",
'valueOf', "valueOf",
]; ];
var native_fns = { var native_fns = {
Array: makePredicate([ Array: [
'indexOf', "indexOf",
'join', "join",
'lastIndexOf', "lastIndexOf",
'slice', "slice",
].concat(object_fns)), ].concat(object_fns),
Boolean: makePredicate(object_fns), Boolean: object_fns,
Number: makePredicate([ Number: [
'toExponential', "toExponential",
'toFixed', "toFixed",
'toPrecision', "toPrecision",
].concat(object_fns)), ].concat(object_fns),
RegExp: makePredicate([ RegExp: [
'test', "test",
].concat(object_fns)), ].concat(object_fns),
String: makePredicate([ String: [
'charAt', "charAt",
'charCodeAt', "charCodeAt",
'concat', "concat",
'indexOf', "indexOf",
'italics', "italics",
'lastIndexOf', "lastIndexOf",
'match', "match",
'replace', "replace",
'search', "search",
'slice', "slice",
'split', "split",
'substr', "substr",
'substring', "substring",
'trim', "trim",
].concat(object_fns)), ].concat(object_fns),
}; };
convert_to_predicate(native_fns);
var static_fns = {
Array: [
"isArray",
],
Math: [
"abs",
"acos",
"asin",
"atan",
"ceil",
"cos",
"exp",
"floor",
"log",
"round",
"sin",
"sqrt",
"tan",
"atan2",
"pow",
"max",
"min"
],
Number: [
"isFinite",
"isNaN",
],
String: [
"fromCharCode",
],
};
convert_to_predicate(static_fns);
def(AST_Call, function(compressor){ def(AST_Call, function(compressor){
var exp = this.expression; var exp = this.expression;
if (compressor.option("unsafe") && exp instanceof AST_PropAccess) { if (compressor.option("unsafe") && exp instanceof AST_PropAccess) {
var key = exp.property; var key = exp.property;
if (key instanceof AST_Node) { if (key instanceof AST_Node) {
key = ev(key, compressor); key = ev(key, compressor);
if (key === exp.property) return this;
} }
var val = ev(exp.expression, compressor); var val;
if ((val && native_fns[val.constructor.name] || return_false)(key)) { var e = exp.expression;
return val[key].apply(val, this.args.map(function(arg) { if (is_undeclared_ref(e)) {
return ev(arg, compressor); if (!(static_fns[e.name] || return_false)(key)) return this;
})); val = global_objs[e.name];
} else {
val = ev(e, compressor);
if (val === e || !(val && native_fns[val.constructor.name] || return_false)(key)) return this;
} }
var args = [];
for (var i = 0, len = this.args.length; i < len; i++) {
var arg = this.args[i];
var value = ev(arg, compressor);
if (arg === value) return this;
args.push(value);
} }
throw def; return val[key].apply(val, args);
}); }
def(AST_New, function(compressor){ return this;
throw def;
}); });
def(AST_New, return_this);
})(function(node, func){ })(function(node, func){
node.DEFMETHOD("_eval", func); node.DEFMETHOD("_eval", func);
}); });
@@ -1891,7 +2058,7 @@ merge(Compressor.prototype, {
|| this.expression.has_side_effects(compressor); || this.expression.has_side_effects(compressor);
}); });
def(AST_SymbolRef, function(compressor){ def(AST_SymbolRef, function(compressor){
return this.undeclared(); return !this.is_declared(compressor);
}); });
def(AST_SymbolDeclaration, return_false); def(AST_SymbolDeclaration, return_false);
def(AST_Object, function(compressor){ def(AST_Object, function(compressor){
@@ -2015,7 +2182,12 @@ merge(Compressor.prototype, {
var drop_funcs = !(self instanceof AST_Toplevel) || compressor.toplevel.funcs; var drop_funcs = !(self instanceof AST_Toplevel) || compressor.toplevel.funcs;
var drop_vars = !(self instanceof AST_Toplevel) || compressor.toplevel.vars; var drop_vars = !(self instanceof AST_Toplevel) || compressor.toplevel.vars;
if (!drop_funcs && !drop_vars) return; if (!drop_funcs && !drop_vars) return;
var assign_as_unused = !/keep_assign/.test(compressor.option("unused")); var assign_as_unused = /keep_assign/.test(compressor.option("unused")) ? return_false : function(node) {
if (node instanceof AST_Assign && (node.write_only || node.operator == "=")) {
return node.left;
}
if (node instanceof AST_Unary && node.write_only) return node.expression;
};
var in_use = []; var in_use = [];
var in_use_ids = Object.create(null); // avoid expensive linear scans of in_use var in_use_ids = Object.create(null); // avoid expensive linear scans of in_use
if (self instanceof AST_Toplevel && compressor.top_retain) { if (self instanceof AST_Toplevel && compressor.top_retain) {
@@ -2065,12 +2237,8 @@ merge(Compressor.prototype, {
}); });
return true; return true;
} }
if (assign_as_unused if (assign_as_unused(node) instanceof AST_SymbolRef && scope === self) {
&& node instanceof AST_Assign if (node instanceof AST_Assign) node.right.walk(tw);
&& node.operator == "="
&& node.left instanceof AST_SymbolRef
&& scope === self) {
node.right.walk(tw);
return true; return true;
} }
if (node instanceof AST_SymbolRef) { if (node instanceof AST_SymbolRef) {
@@ -2234,15 +2402,18 @@ merge(Compressor.prototype, {
}); });
} }
} }
if (drop_vars && assign_as_unused if (drop_vars) {
&& node instanceof AST_Assign var def = assign_as_unused(node);
&& node.operator == "=" if (def instanceof AST_SymbolRef
&& node.left instanceof AST_SymbolRef) { && !((def = def.definition()).id in in_use_ids)
var def = node.left.definition();
if (!(def.id in in_use_ids)
&& self.variables.get(def.name) === def) { && self.variables.get(def.name) === def) {
if (node instanceof AST_Assign) {
return maintain_this_binding(tt.parent(), node, node.right.transform(tt)); return maintain_this_binding(tt.parent(), node, node.right.transform(tt));
} }
return make_node(AST_Number, node, {
value: 0
});
}
} }
// certain combination of unused name + side effect leads to: // certain combination of unused name + side effect leads to:
// https://github.com/mishoo/UglifyJS2/issues/44 // https://github.com/mishoo/UglifyJS2/issues/44
@@ -2480,7 +2651,10 @@ merge(Compressor.prototype, {
return make_sequence(this, [ left, right ]); return make_sequence(this, [ left, right ]);
} }
}); });
def(AST_Assign, return_this); def(AST_Assign, function(compressor){
this.write_only = !this.left.has_side_effects(compressor);
return this;
});
def(AST_Conditional, function(compressor){ def(AST_Conditional, function(compressor){
var consequent = this.consequent.drop_side_effect_free(compressor); var consequent = this.consequent.drop_side_effect_free(compressor);
var alternative = this.alternative.drop_side_effect_free(compressor); var alternative = this.alternative.drop_side_effect_free(compressor);
@@ -2501,7 +2675,10 @@ merge(Compressor.prototype, {
return node; return node;
}); });
def(AST_Unary, function(compressor, first_in_statement){ def(AST_Unary, function(compressor, first_in_statement){
if (unary_side_effects(this.operator)) return this; if (unary_side_effects(this.operator)) {
this.write_only = !this.expression.has_side_effects(compressor);
return this;
}
if (this.operator == "typeof" && this.expression instanceof AST_SymbolRef) return null; if (this.operator == "typeof" && this.expression instanceof AST_SymbolRef) return null;
var expression = this.expression.drop_side_effect_free(compressor, first_in_statement); var expression = this.expression.drop_side_effect_free(compressor, first_in_statement);
if (first_in_statement if (first_in_statement
@@ -2515,8 +2692,8 @@ merge(Compressor.prototype, {
} }
return expression; return expression;
}); });
def(AST_SymbolRef, function() { def(AST_SymbolRef, function(compressor) {
return this.undeclared() ? this : null; return this.is_declared(compressor) ? null : this;
}); });
def(AST_Object, function(compressor, first_in_statement){ def(AST_Object, function(compressor, first_in_statement){
var values = trim(this.properties, compressor, first_in_statement); var values = trim(this.properties, compressor, first_in_statement);
@@ -2978,7 +3155,7 @@ merge(Compressor.prototype, {
self.args.length = last; self.args.length = last;
} }
if (compressor.option("unsafe")) { if (compressor.option("unsafe")) {
if (exp instanceof AST_SymbolRef && exp.undeclared()) { if (is_undeclared_ref(exp)) {
switch (exp.name) { switch (exp.name) {
case "Array": case "Array":
if (self.args.length != 1) { if (self.args.length != 1) {
@@ -3105,8 +3282,7 @@ merge(Compressor.prototype, {
} }
} }
if (compressor.option("unsafe_Func") if (compressor.option("unsafe_Func")
&& exp instanceof AST_SymbolRef && is_undeclared_ref(exp)
&& exp.undeclared()
&& exp.name == "Function") { && exp.name == "Function") {
// new Function() => function(){} // new Function() => function(){}
if (self.args.length == 0) return make_node(AST_Function, self, { if (self.args.length == 0) return make_node(AST_Function, self, {
@@ -3168,80 +3344,53 @@ merge(Compressor.prototype, {
var value = stat.value; var value = stat.value;
if (!value || value.is_constant_expression()) { if (!value || value.is_constant_expression()) {
var args = self.args.concat(value || make_node(AST_Undefined, self)); var args = self.args.concat(value || make_node(AST_Undefined, self));
return make_sequence(self, args).transform(compressor); return make_sequence(self, args).optimize(compressor);
} }
} }
if (exp instanceof AST_Function) { if (exp instanceof AST_Function) {
if (compressor.option("inline") if (compressor.option("inline")
&& !exp.name && !exp.name
&& exp.body.length == 1
&& !exp.uses_arguments && !exp.uses_arguments
&& !exp.uses_eval && !exp.uses_eval
&& exp.body.length == 1
&& all(exp.argnames, function(arg) {
return arg.__unused;
})
&& !self.has_pure_annotation(compressor)) { && !self.has_pure_annotation(compressor)) {
var value; var value;
if (stat instanceof AST_Return) { if (stat instanceof AST_Return) {
value = stat.value.clone(true); value = stat.value;
} else if (stat instanceof AST_SimpleStatement) { } else if (stat instanceof AST_SimpleStatement) {
value = make_node(AST_UnaryPrefix, stat, { value = make_node(AST_UnaryPrefix, stat, {
operator: "void", operator: "void",
expression: stat.body.clone(true) expression: stat.body
}); });
} }
if (value) { if (value) {
var fn = exp.clone();
fn.argnames = [];
fn.body = [];
if (exp.argnames.length > 0) {
fn.body.push(make_node(AST_Var, self, {
definitions: exp.argnames.map(function(sym, i) {
var arg = self.args[i];
return make_node(AST_VarDef, sym, {
name: sym,
value: arg ? arg.clone(true) : make_node(AST_Undefined, self)
});
})
}));
}
if (self.args.length > exp.argnames.length) {
fn.body.push(make_node(AST_SimpleStatement, self, {
body: make_sequence(self, self.args.slice(exp.argnames.length).map(function(node) {
return node.clone(true);
}))
}));
}
fn.body.push(make_node(AST_Return, self, {
value: value
}));
var body = fn.transform(compressor).body;
if (body.length == 0) return make_node(AST_Undefined, self);
if (body.length == 1 && body[0] instanceof AST_Return) {
value = body[0].value;
if (!value) return make_node(AST_Undefined, self);
var tw = new TreeWalker(function(node) { var tw = new TreeWalker(function(node) {
if (value === self) return true; if (!value) return true;
if (node instanceof AST_SymbolRef) { if (node instanceof AST_SymbolRef) {
var ref = node.scope.find_variable(node); var ref = node.scope.find_variable(node);
if (ref && ref.scope.parent_scope === fn.parent_scope) { if (ref && ref.scope.parent_scope === fn.parent_scope) {
value = self; value = null;
return true; return true;
} }
} }
if (node instanceof AST_This && !tw.find_parent(AST_Scope)) { if (node instanceof AST_This && !tw.find_parent(AST_Scope)) {
value = self; value = null;
return true; return true;
} }
}); });
value.walk(tw); value.walk(tw);
if (value !== self) value = best_of(compressor, value, self);
} else {
value = self;
} }
if (value !== self) return value; if (value) {
var args = self.args.concat(value);
return make_sequence(self, args).optimize(compressor);
} }
} }
if (compressor.option("side_effects") && all(exp.body, is_empty)) { if (compressor.option("side_effects") && all(exp.body, is_empty)) {
var args = self.args.concat(make_node(AST_Undefined, self)); var args = self.args.concat(make_node(AST_Undefined, self));
return make_sequence(self, args).transform(compressor); return make_sequence(self, args).optimize(compressor);
} }
} }
if (compressor.option("drop_console")) { if (compressor.option("drop_console")) {
@@ -3250,9 +3399,7 @@ merge(Compressor.prototype, {
while (name.expression) { while (name.expression) {
name = name.expression; name = name.expression;
} }
if (name instanceof AST_SymbolRef if (is_undeclared_ref(name) && name.name == "console") {
&& name.name == "console"
&& name.undeclared()) {
return make_node(AST_Undefined, self).optimize(compressor); return make_node(AST_Undefined, self).optimize(compressor);
} }
} }
@@ -3273,7 +3420,7 @@ merge(Compressor.prototype, {
OPT(AST_New, function(self, compressor){ OPT(AST_New, function(self, compressor){
if (compressor.option("unsafe")) { if (compressor.option("unsafe")) {
var exp = self.expression; var exp = self.expression;
if (exp instanceof AST_SymbolRef && exp.undeclared()) { if (is_undeclared_ref(exp)) {
switch (exp.name) { switch (exp.name) {
case "Object": case "Object":
case "RegExp": case "RegExp":
@@ -3350,6 +3497,8 @@ merge(Compressor.prototype, {
operator: car.operator, operator: car.operator,
expression: left expression: left
}); });
} else {
car.write_only = false;
} }
if (parent) { if (parent) {
parent[field] = car; parent[field] = car;
@@ -3559,12 +3708,13 @@ merge(Compressor.prototype, {
case "==": case "==":
case "!=": case "!=":
// "undefined" == typeof x => undefined === x // "undefined" == typeof x => undefined === x
if (self.left instanceof AST_String if (compressor.option("typeofs")
&& self.left instanceof AST_String
&& self.left.value == "undefined" && self.left.value == "undefined"
&& self.right instanceof AST_UnaryPrefix && self.right instanceof AST_UnaryPrefix
&& self.right.operator == "typeof") { && self.right.operator == "typeof") {
var expr = self.right.expression; var expr = self.right.expression;
if (expr instanceof AST_SymbolRef ? !expr.undeclared() if (expr instanceof AST_SymbolRef ? expr.is_declared(compressor)
: !(expr instanceof AST_PropAccess && compressor.option("ie8"))) { : !(expr instanceof AST_PropAccess && compressor.option("ie8"))) {
self.right = expr; self.right = expr;
self.left = make_node(AST_Undefined, self.left).optimize(compressor); self.left = make_node(AST_Undefined, self.left).optimize(compressor);
@@ -3890,7 +4040,7 @@ merge(Compressor.prototype, {
} }
// testing against !self.scope.uses_with first is an optimization // testing against !self.scope.uses_with first is an optimization
if (!compressor.option("ie8") if (!compressor.option("ie8")
&& self.undeclared() && is_undeclared_ref(self)
&& (!self.scope.uses_with || !compressor.find_parent(AST_With))) { && (!self.scope.uses_with || !compressor.find_parent(AST_With))) {
switch (self.name) { switch (self.name) {
case "undefined": case "undefined":
@@ -4245,7 +4395,7 @@ merge(Compressor.prototype, {
var prop = self.property; var prop = self.property;
if (prop instanceof AST_String && compressor.option("properties")) { if (prop instanceof AST_String && compressor.option("properties")) {
prop = prop.getValue(); prop = prop.getValue();
if (RESERVED_WORDS(prop) ? !compressor.option("ie8") : is_identifier_string(prop)) { if (is_identifier_string(prop)) {
return make_node(AST_Dot, self, { return make_node(AST_Dot, self, {
expression : self.expression, expression : self.expression,
property : prop property : prop
@@ -4266,25 +4416,41 @@ merge(Compressor.prototype, {
return self; return self;
}); });
AST_Lambda.DEFMETHOD("contains_this", function() {
var result;
var self = this;
self.walk(new TreeWalker(function(node) {
if (result) return true;
if (node instanceof AST_This) return result = true;
if (node !== self && node instanceof AST_Scope) return true;
}));
return result;
});
OPT(AST_Dot, function(self, compressor){ OPT(AST_Dot, function(self, compressor){
var def = self.resolve_defines(compressor); var def = self.resolve_defines(compressor);
if (def) { if (def) {
return def.optimize(compressor); return def.optimize(compressor);
} }
var prop = self.property; if (compressor.option("unsafe") && self.expression instanceof AST_Object) {
if (RESERVED_WORDS(prop) && compressor.option("ie8")) { var values = self.expression.properties;
return make_node(AST_Sub, self, { for (var i = values.length; --i >= 0;) {
expression : self.expression, if (values[i].key === self.property) {
property : make_node(AST_String, self, { var value = values[i].value;
value: prop if (value instanceof AST_Function ? !value.contains_this() : !value.has_side_effects(compressor)) {
}) var obj = self.expression.clone();
}).optimize(compressor); obj.properties = obj.properties.slice();
obj.properties.splice(i, 1);
return make_sequence(self, [ obj, value ]).optimize(compressor);
}
}
}
} }
if (compressor.option("unsafe_proto") if (compressor.option("unsafe_proto")
&& self.expression instanceof AST_Dot && self.expression instanceof AST_Dot
&& self.expression.property == "prototype") { && self.expression.property == "prototype") {
var exp = self.expression.expression; var exp = self.expression.expression;
if (exp instanceof AST_SymbolRef && exp.undeclared()) switch (exp.name) { if (is_undeclared_ref(exp)) switch (exp.name) {
case "Array": case "Array":
self.expression = make_node(AST_Array, self.expression, { self.expression = make_node(AST_Array, self.expression, {
elements: [] elements: []

View File

@@ -27,6 +27,23 @@ function set_shorthand(name, options, keys) {
} }
} }
function init_cache(cache) {
if (!cache) return;
if (!("cname" in cache)) cache.cname = -1;
if (!("props" in cache)) {
cache.props = new Dictionary();
} else if (!(cache.props instanceof Dictionary)) {
cache.props = Dictionary.fromObject(cache.props);
}
}
function to_json(cache) {
return {
cname: cache.cname,
props: cache.props.toObject()
};
}
function minify(files, options) { function minify(files, options) {
var warn_function = AST_Node.warn_function; var warn_function = AST_Node.warn_function;
try { try {
@@ -35,6 +52,7 @@ function minify(files, options) {
ie8: false, ie8: false,
keep_fnames: false, keep_fnames: false,
mangle: {}, mangle: {},
nameCache: null,
output: {}, output: {},
parse: {}, parse: {},
sourceMap: false, sourceMap: false,
@@ -52,7 +70,7 @@ function minify(files, options) {
set_shorthand("warnings", options, [ "compress" ]); set_shorthand("warnings", options, [ "compress" ]);
if (options.mangle) { if (options.mangle) {
options.mangle = defaults(options.mangle, { options.mangle = defaults(options.mangle, {
cache: null, cache: options.nameCache && (options.nameCache.vars || {}),
eval: false, eval: false,
ie8: false, ie8: false,
keep_fnames: false, keep_fnames: false,
@@ -60,6 +78,16 @@ function minify(files, options) {
reserved: [], reserved: [],
toplevel: false, toplevel: false,
}, true); }, true);
if (options.nameCache && options.mangle.properties) {
if (typeof options.mangle.properties != "object") {
options.mangle.properties = {};
}
if (!("cache" in options.mangle.properties)) {
options.mangle.properties.cache = options.nameCache.props || {};
}
}
init_cache(options.mangle.cache);
init_cache(options.mangle.properties.cache);
} }
if (options.sourceMap) { if (options.sourceMap) {
options.sourceMap = defaults(options.sourceMap, { options.sourceMap = defaults(options.sourceMap, {
@@ -153,6 +181,12 @@ function minify(files, options) {
} }
} }
} }
if (options.nameCache && options.mangle) {
if (options.mangle.cache) options.nameCache.vars = to_json(options.mangle.cache);
if (options.mangle.properties && options.mangle.properties.cache) {
options.nameCache.props = to_json(options.mangle.properties.cache);
}
}
if (timings) { if (timings) {
timings.end = Date.now(); timings.end = Date.now();
result.timings = { result.timings = {

View File

@@ -672,14 +672,15 @@ function OutputStream(options) {
// parens around it too, otherwise the call will be // parens around it too, otherwise the call will be
// interpreted as passing the arguments to the upper New // interpreted as passing the arguments to the upper New
// expression. // expression.
try { var parens = false;
this.walk(new TreeWalker(function(node){ this.walk(new TreeWalker(function(node) {
if (node instanceof AST_Call) throw p; if (parens || node instanceof AST_Scope) return true;
})); if (node instanceof AST_Call) {
} catch(ex) { parens = true;
if (ex !== p) throw ex;
return true; return true;
} }
}));
return parens;
} }
}); });
@@ -1073,19 +1074,17 @@ function OutputStream(options) {
}); });
function parenthesize_for_noin(node, output, noin) { function parenthesize_for_noin(node, output, noin) {
if (!noin) node.print(output); var parens = false;
else try {
// need to take some precautions here: // need to take some precautions here:
// https://github.com/mishoo/UglifyJS2/issues/60 // https://github.com/mishoo/UglifyJS2/issues/60
node.walk(new TreeWalker(function(node){ if (noin) node.walk(new TreeWalker(function(node) {
if (node instanceof AST_Binary && node.operator == "in") if (parens || node instanceof AST_Scope) return true;
throw output; if (node instanceof AST_Binary && node.operator == "in") {
})); parens = true;
node.print(output); return true;
} catch(ex) {
if (ex !== output) throw ex;
node.print(output, true);
} }
}));
node.print(output, parens);
}; };
DEFPRINT(AST_VarDef, function(self, output){ DEFPRINT(AST_VarDef, function(self, output){
@@ -1105,6 +1104,9 @@ function OutputStream(options) {
self.expression.print(output); self.expression.print(output);
if (self instanceof AST_New && !need_constructor_parens(self, output)) if (self instanceof AST_New && !need_constructor_parens(self, output))
return; return;
if (self.expression instanceof AST_Lambda) {
output.add_mapping(self.start);
}
output.with_parens(function(){ output.with_parens(function(){
self.args.forEach(function(expr, i){ self.args.forEach(function(expr, i){
if (i) output.comma(); if (i) output.comma();
@@ -1144,6 +1146,13 @@ function OutputStream(options) {
DEFPRINT(AST_Dot, function(self, output){ DEFPRINT(AST_Dot, function(self, output){
var expr = self.expression; var expr = self.expression;
expr.print(output); expr.print(output);
var prop = self.property;
if (output.option("ie8") && RESERVED_WORDS(prop)) {
output.print("[");
output.add_mapping(self.end);
output.print_string(prop);
output.print("]");
} else {
if (expr instanceof AST_Number && expr.getValue() >= 0) { if (expr instanceof AST_Number && expr.getValue() >= 0) {
if (!/[xa-f.)]/i.test(output.last())) { if (!/[xa-f.)]/i.test(output.last())) {
output.print("."); output.print(".");
@@ -1152,7 +1161,8 @@ function OutputStream(options) {
output.print("."); output.print(".");
// the name after dot would be mapped about here. // the name after dot would be mapped about here.
output.add_mapping(self.end); output.add_mapping(self.end);
output.print_name(self.property); output.print_name(prop);
}
}); });
DEFPRINT(AST_Sub, function(self, output){ DEFPRINT(AST_Sub, function(self, output){
self.expression.print(output); self.expression.print(output);

View File

@@ -76,7 +76,7 @@ function mangle_properties(ast, options) {
only_cache: false, only_cache: false,
regex: null, regex: null,
reserved: null, reserved: null,
}); }, true);
var reserved = options.reserved; var reserved = options.reserved;
if (!Array.isArray(reserved)) reserved = []; if (!Array.isArray(reserved)) reserved = [];

View File

@@ -373,14 +373,6 @@ AST_Symbol.DEFMETHOD("unreferenced", function(){
&& !(this.scope.uses_eval || this.scope.uses_with); && !(this.scope.uses_eval || this.scope.uses_with);
}); });
AST_Symbol.DEFMETHOD("undeclared", function(){
return this.definition().undeclared;
});
AST_LabelRef.DEFMETHOD("undeclared", return_false);
AST_Label.DEFMETHOD("undeclared", return_false);
AST_Symbol.DEFMETHOD("definition", function(){ AST_Symbol.DEFMETHOD("definition", function(){
return this.thedef; return this.thedef;
}); });

View File

@@ -70,7 +70,7 @@ TreeTransformer.prototype = new TreeWalker;
if (y !== undefined) x = y; if (y !== undefined) x = y;
} }
} }
tw.pop(this); tw.pop();
return x; return x;
}); });
}; };

View File

@@ -4,7 +4,7 @@
"homepage": "http://lisperator.net/uglifyjs", "homepage": "http://lisperator.net/uglifyjs",
"author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)", "author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"version": "3.0.21", "version": "3.0.25",
"engines": { "engines": {
"node": ">=0.8.0" "node": ">=0.8.0"
}, },

View File

@@ -6,6 +6,7 @@
var createHash = require("crypto").createHash; var createHash = require("crypto").createHash;
var fetch = require("./fetch"); var fetch = require("./fetch");
var fork = require("child_process").fork; var fork = require("child_process").fork;
var zlib = require("zlib");
var args = process.argv.slice(2); var args = process.argv.slice(2);
if (!args.length) { if (!args.length) {
args.push("-mc"); args.push("-mc");
@@ -33,6 +34,7 @@ function done() {
console.log(info.log); console.log(info.log);
console.log("Original:", info.input, "bytes"); console.log("Original:", info.input, "bytes");
console.log("Uglified:", info.output, "bytes"); console.log("Uglified:", info.output, "bytes");
console.log("GZipped: ", info.gzip, "bytes");
console.log("SHA1 sum:", info.sha1); console.log("SHA1 sum:", info.sha1);
if (info.code) { if (info.code) {
failures.push(url); failures.push(url);
@@ -51,6 +53,7 @@ urls.forEach(function(url) {
results[url] = { results[url] = {
input: 0, input: 0,
output: 0, output: 0,
gzip: 0,
log: "" log: ""
}; };
fetch(url, function(err, res) { fetch(url, function(err, res) {
@@ -61,6 +64,10 @@ urls.forEach(function(url) {
}).pipe(uglifyjs.stdin); }).pipe(uglifyjs.stdin);
uglifyjs.stdout.on("data", function(data) { uglifyjs.stdout.on("data", function(data) {
results[url].output += data.length; results[url].output += data.length;
}).pipe(zlib.createGzip({
level: zlib.Z_BEST_COMPRESSION
})).on("data", function(data) {
results[url].gzip += data.length;
}).pipe(createHash("sha1")).on("data", function(data) { }).pipe(createHash("sha1")).on("data", function(data) {
results[url].sha1 = data.toString("hex"); results[url].sha1 = data.toString("hex");
done(); done();

View File

@@ -863,7 +863,7 @@ collapse_vars_unary: {
input: { input: {
function f0(o, p) { function f0(o, p) {
var x = o[p]; var x = o[p];
delete x; return delete x;
} }
function f1(n) { function f1(n) {
var k = !!n; var k = !!n;
@@ -893,7 +893,7 @@ collapse_vars_unary: {
expect: { expect: {
function f0(o, p) { function f0(o, p) {
var x = o[p]; var x = o[p];
delete x; return delete x;
} }
function f1(n) { function f1(n) {
return n > +!!n return n > +!!n
@@ -1978,10 +1978,10 @@ chained_3: {
} }
expect: { expect: {
console.log(function(a, b) { console.log(function(a, b) {
var c = a, c = b; var c = 1, c = b;
b++; b++;
return c; return c;
}(1, 2)); }(0, 2));
} }
expect_stdout: "2" expect_stdout: "2"
} }
@@ -2186,3 +2186,159 @@ compound_assignment: {
} }
expect_stdout: "4" expect_stdout: "4"
} }
issue_2187_1: {
options = {
collapse_vars: true,
unused: true,
}
input: {
var a = 1;
!function(foo) {
foo();
var a = 2;
console.log(a);
}(function() {
console.log(a);
});
}
expect: {
var a = 1;
!function(foo) {
foo();
var a = 2;
console.log(a);
}(function() {
console.log(a);
});
}
expect_stdout: [
"1",
"2",
]
}
issue_2187_2: {
options = {
collapse_vars: true,
unused: true,
}
input: {
var b = 1;
console.log(function(a) {
return a && ++b;
}(b--));
}
expect: {
var b = 1;
console.log(function(a) {
return b-- && ++b;
}());
}
expect_stdout: "1"
}
issue_2187_3: {
options = {
collapse_vars: true,
inline: true,
unused: true,
}
input: {
var b = 1;
console.log(function(a) {
return a && ++b;
}(b--));
}
expect: {
var b = 1;
console.log(b-- && ++b);
}
expect_stdout: "1"
}
issue_2203_1: {
options = {
collapse_vars: true,
unused: true,
}
input: {
a = "FAIL";
console.log({
a: "PASS",
b: function() {
return function(c) {
return c.a;
}((String, (Object, this)));
}
}.b());
}
expect: {
a = "FAIL";
console.log({
a: "PASS",
b: function() {
return function(c) {
return c.a;
}((String, (Object, this)));
}
}.b());
}
expect_stdout: "PASS"
}
issue_2203_2: {
options = {
collapse_vars: true,
unused: true,
}
input: {
a = "PASS";
console.log({
a: "FAIL",
b: function() {
return function(c) {
return c.a;
}((String, (Object, function() {
return this;
}())));
}
}.b());
}
expect: {
a = "PASS";
console.log({
a: "FAIL",
b: function() {
return function(c) {
return (String, (Object, function() {
return this;
}())).a;
}();
}
}.b());
}
expect_stdout: "PASS"
}
duplicate_argname: {
options = {
collapse_vars: true,
unused: true,
}
input: {
function f() { return "PASS"; }
console.log(function(a, a) {
f++;
return a;
}("FAIL", f()));
}
expect: {
function f() { return "PASS"; }
console.log(function(a, a) {
f++;
return a;
}("FAIL", f()));
}
expect_stdout: "PASS"
}

View File

@@ -230,3 +230,82 @@ accessor: {
} }
expect: {} expect: {}
} }
issue_2233_1: {
options = {
pure_getters: "strict",
side_effects: true,
unsafe: true,
}
input: {
Array.isArray;
Boolean;
console.log;
Error.name;
Function.length;
Math.random;
Number.isNaN;
RegExp;
Object.defineProperty;
String.fromCharCode;
}
expect: {}
expect_stdout: true
}
issue_2233_2: {
options = {
pure_getters: "strict",
reduce_vars: true,
side_effects: true,
unsafe: true,
unused: true,
}
input: {
var RegExp;
Array.isArray;
RegExp;
UndeclaredGlobal;
function foo() {
var Number;
AnotherUndeclaredGlobal;
Math.sin;
Number.isNaN;
}
}
expect: {
var RegExp;
UndeclaredGlobal;
function foo() {
var Number;
AnotherUndeclaredGlobal;
Number.isNaN;
}
}
}
issue_2233_3: {
options = {
pure_getters: "strict",
reduce_vars: true,
side_effects: true,
toplevel: true,
unsafe: true,
unused: true,
}
input: {
var RegExp;
Array.isArray;
RegExp;
UndeclaredGlobal;
function foo() {
var Number;
AnotherUndeclaredGlobal;
Math.sin;
Number.isNaN;
}
}
expect: {
UndeclaredGlobal;
}
}

View File

@@ -1090,6 +1090,7 @@ var_catch_toplevel: {
a--; a--;
try { try {
a++; a++;
x();
} catch(a) { } catch(a) {
if (a) var a; if (a) var a;
var a = 10; var a = 10;
@@ -1099,9 +1100,8 @@ var_catch_toplevel: {
} }
expect: { expect: {
!function() { !function() {
a--;
try { try {
a++; x();
} catch(a) { } catch(a) {
var a; var a;
} }
@@ -1113,6 +1113,7 @@ issue_2105: {
options = { options = {
collapse_vars: true, collapse_vars: true,
inline: true, inline: true,
passes: 3,
reduce_vars: true, reduce_vars: true,
side_effects: true, side_effects: true,
unused: true, unused: true,
@@ -1138,7 +1139,7 @@ issue_2105: {
}); });
} }
expect: { expect: {
!void function() { (function() {
var quux = function() { var quux = function() {
console.log("PASS"); console.log("PASS");
}; };
@@ -1148,7 +1149,93 @@ issue_2105: {
quux(); quux();
} }
}; };
}().prop(); })().prop();
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
issue_2226_1: {
options = {
side_effects: true,
unused: true,
}
input: {
function f1() {
var a = b;
a += c;
}
function f2(a) {
a <<= b;
}
function f3(a) {
--a;
}
function f4() {
var a = b;
return a *= c;
}
function f5(a) {
x(a /= b);
}
}
expect: {
function f1() {
b;
c;
}
function f2(a) {
b;
}
function f3(a) {
0;
}
function f4() {
var a = b;
return a *= c;
}
function f5(a) {
x(a /= b);
}
}
}
issue_2226_2: {
options = {
cascade: true,
sequences: true,
side_effects: true,
unused: true,
}
input: {
console.log(function(a, b) {
a += b;
return a;
}(1, 2));
}
expect: {
console.log(function(a, b) {
return a += b;
}(1, 2));
}
expect_stdout: "3"
}
issue_2226_3: {
options = {
collapse_vars: true,
side_effects: true,
unused: true,
}
input: {
console.log(function(a, b) {
a += b;
return a;
}(1, 2));
}
expect: {
console.log(function(a, b) {
return a += 2;
}(1));
}
expect_stdout: "3"
}

View File

@@ -250,22 +250,26 @@ unsafe_constant: {
unsafe_object: { unsafe_object: {
options = { options = {
evaluate : true, evaluate: true,
unsafe : true reduce_vars: true,
toplevel: true,
unsafe: true,
} }
input: { input: {
var o = { a: 1 };
console.log( console.log(
({a:1}) + 1, o + 1,
({a:1}).a + 1, o.a + 1,
({a:1}).b + 1, o.b + 1,
({a:1}).a.b + 1 o.a.b + 1
); );
} }
expect: { expect: {
var o = { a: 1 };
console.log( console.log(
({a:1}) + 1, o + 1,
2, 2,
({a:1}).b + 1, o.b + 1,
1..b + 1 1..b + 1
); );
} }
@@ -274,22 +278,26 @@ unsafe_object: {
unsafe_object_nested: { unsafe_object_nested: {
options = { options = {
evaluate : true, evaluate: true,
unsafe : true reduce_vars: true,
toplevel: true,
unsafe: true,
} }
input: { input: {
var o = { a: { b: 1 } };
console.log( console.log(
({a:{b:1}}) + 1, o + 1,
({a:{b:1}}).a + 1, o.a + 1,
({a:{b:1}}).b + 1, o.b + 1,
({a:{b:1}}).a.b + 1 o.a.b + 1
); );
} }
expect: { expect: {
var o = { a: { b: 1 } };
console.log( console.log(
({a:{b:1}}) + 1, o + 1,
({a:{b:1}}).a + 1, o.a + 1,
({a:{b:1}}).b + 1, o.b + 1,
2 2
); );
} }
@@ -298,21 +306,25 @@ unsafe_object_nested: {
unsafe_object_complex: { unsafe_object_complex: {
options = { options = {
evaluate : true, evaluate: true,
unsafe : true reduce_vars: true,
toplevel: true,
unsafe: true,
} }
input: { input: {
var o = { a: { b: 1 }, b: 1 };
console.log( console.log(
({a:{b:1},b:1}) + 1, o + 1,
({a:{b:1},b:1}).a + 1, o.a + 1,
({a:{b:1},b:1}).b + 1, o.b + 1,
({a:{b:1},b:1}).a.b + 1 o.a.b + 1
); );
} }
expect: { expect: {
var o = { a: { b: 1 }, b: 1 };
console.log( console.log(
({a:{b:1},b:1}) + 1, o + 1,
({a:{b:1},b:1}).a + 1, o.a + 1,
2, 2,
2 2
); );
@@ -322,22 +334,26 @@ unsafe_object_complex: {
unsafe_object_repeated: { unsafe_object_repeated: {
options = { options = {
evaluate : true, evaluate: true,
unsafe : true reduce_vars: true,
toplevel: true,
unsafe: true,
} }
input: { input: {
var o = { a: { b: 1 }, a: 1 };
console.log( console.log(
({a:{b:1},a:1}) + 1, o + 1,
({a:{b:1},a:1}).a + 1, o.a + 1,
({a:{b:1},a:1}).b + 1, o.b + 1,
({a:{b:1},a:1}).a.b + 1 o.a.b + 1
); );
} }
expect: { expect: {
var o = { a: { b: 1 }, a: 1 };
console.log( console.log(
({a:{b:1},a:1}) + 1, o + 1,
2, 2,
({a:{b:1},a:1}).b + 1, o.b + 1,
1..b + 1 1..b + 1
); );
} }
@@ -386,9 +402,9 @@ unsafe_function: {
expect: { expect: {
console.log( console.log(
({a:{b:1},b:function(){}}) + 1, ({a:{b:1},b:function(){}}) + 1,
({a:{b:1},b:function(){}}).a + 1, ({b:function(){}}, {b:1}) + 1,
({a:{b:1},b:function(){}}).b + 1, ({a:{b:1}}, function(){}) + 1,
({a:{b:1},b:function(){}}).a.b + 1 ({b:function(){}}, {b:1}).b + 1
); );
} }
expect_stdout: true expect_stdout: true
@@ -636,8 +652,8 @@ unsafe_prototype_function: {
var d = ({toString: 0}) + ""; var d = ({toString: 0}) + "";
var e = (({valueOf: 0}) + "")[2]; var e = (({valueOf: 0}) + "")[2];
var f = (({toString: 0}) + "")[2]; var f = (({toString: 0}) + "")[2];
var g = ({valueOf: 0}).valueOf(); var g = ({}, 0)();
var h = "" + ({toString: 0}); var h = ({}, 0)();
} }
} }
@@ -1069,3 +1085,103 @@ string_charCodeAt: {
} }
expect_stdout: "NaN" expect_stdout: "NaN"
} }
issue_2207_1: {
options = {
evaluate: true,
unsafe: true,
}
input: {
console.log(String.fromCharCode(65));
console.log(Math.max(3, 6, 2, 7, 3, 4));
console.log(Math.cos(1.2345));
console.log(Math.cos(1.2345) - Math.sin(4.321));
console.log(Math.pow(Math.PI, Math.E - Math.LN10));
}
expect: {
console.log("A");
console.log(7);
console.log(Math.cos(1.2345));
console.log(1.2543732512566947);
console.log(1.6093984514472044);
}
expect_stdout: true
}
issue_2207_2: {
options = {
evaluate: true,
unsafe: true,
}
input: {
console.log(Math.E);
console.log(Math.LN10);
console.log(Math.LN2);
console.log(Math.LOG2E);
console.log(Math.LOG10E);
console.log(Math.PI);
console.log(Math.SQRT1_2);
console.log(Math.SQRT2);
}
expect: {
console.log(Math.E);
console.log(Math.LN10);
console.log(Math.LN2);
console.log(Math.LOG2E);
console.log(Math.LOG10E);
console.log(Math.PI);
console.log(Math.SQRT1_2);
console.log(Math.SQRT2);
}
expect_stdout: true
}
issue_2207_3: {
options = {
evaluate: true,
unsafe: true,
}
input: {
console.log(Number.MAX_VALUE);
console.log(Number.MIN_VALUE);
console.log(Number.NaN);
console.log(Number.NEGATIVE_INFINITY);
console.log(Number.POSITIVE_INFINITY);
}
expect: {
console.log(Number.MAX_VALUE);
console.log(5e-324);
console.log(NaN);
console.log(-1/0);
console.log(1/0);
}
expect_stdout: true
}
issue_2231_1: {
options = {
evaluate: true,
unsafe: true,
}
input: {
console.log(Object.keys(void 0));
}
expect: {
console.log(Object.keys(void 0));
}
expect_stdout: true
}
issue_2231_2: {
options = {
evaluate: true,
unsafe: true,
}
input: {
console.log(Object.getOwnPropertyNames(null));
}
expect: {
console.log(Object.getOwnPropertyNames(null));
}
expect_stdout: true
}

View File

@@ -468,11 +468,9 @@ issue_2114_1: {
} }
expect: { expect: {
var c = 0; var c = 0;
!function() { c = 1 + (c += 1), function() {
0;
}((c += 1, c = 1 + c, function() {
var b = void (b && (b.b += (c += 1, 0))); var b = void (b && (b.b += (c += 1, 0)));
}())); }();
console.log(c); console.log(c);
} }
expect_stdout: "2" expect_stdout: "2"

View File

@@ -37,6 +37,7 @@ object: {
VALUE: 42, VALUE: 42,
}, },
}, },
side_effects: true,
unsafe: true, unsafe: true,
} }
input: { input: {
@@ -140,9 +141,9 @@ mixed: {
console.log(CONFIG); console.log(CONFIG);
} }
expect_warnings: [ expect_warnings: [
'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:126,22]',
'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:127,22]', 'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:127,22]',
'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:129,8]', 'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:128,22]',
'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:130,8]',
] ]
} }

View File

@@ -71,11 +71,13 @@ non_hoisted_function_after_return_2a: {
"WARN: Declarations in unreachable code! [test/compress/issue-1034.js:51,16]", "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:51,16]",
"WARN: Dropping unused variable a [test/compress/issue-1034.js:48,20]", "WARN: Dropping unused variable a [test/compress/issue-1034.js:48,20]",
"WARN: Dropping unused function nope [test/compress/issue-1034.js:55,21]", "WARN: Dropping unused function nope [test/compress/issue-1034.js:55,21]",
"WARN: pass 0: last_count: Infinity, count: 37",
"WARN: Dropping unreachable code [test/compress/issue-1034.js:53,12]", "WARN: Dropping unreachable code [test/compress/issue-1034.js:53,12]",
"WARN: Declarations in unreachable code! [test/compress/issue-1034.js:53,12]", "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:53,12]",
"WARN: Dropping unreachable code [test/compress/issue-1034.js:56,12]", "WARN: Dropping unreachable code [test/compress/issue-1034.js:56,12]",
"WARN: Dropping unused variable b [test/compress/issue-1034.js:51,20]", "WARN: Dropping unused variable b [test/compress/issue-1034.js:51,20]",
"WARN: Dropping unused variable c [test/compress/issue-1034.js:53,16]", "WARN: Dropping unused variable c [test/compress/issue-1034.js:53,16]",
"WARN: pass 1: last_count: 37, count: 18",
] ]
} }
@@ -109,11 +111,11 @@ non_hoisted_function_after_return_2b: {
} }
expect_warnings: [ expect_warnings: [
// duplicate warnings no longer emitted // duplicate warnings no longer emitted
"WARN: Dropping unreachable code [test/compress/issue-1034.js:95,16]", "WARN: Dropping unreachable code [test/compress/issue-1034.js:97,16]",
"WARN: Declarations in unreachable code! [test/compress/issue-1034.js:95,16]", "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:97,16]",
"WARN: Dropping unreachable code [test/compress/issue-1034.js:97,12]", "WARN: Dropping unreachable code [test/compress/issue-1034.js:99,12]",
"WARN: Declarations in unreachable code! [test/compress/issue-1034.js:97,12]", "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:99,12]",
"WARN: Dropping unreachable code [test/compress/issue-1034.js:101,12]", "WARN: Dropping unreachable code [test/compress/issue-1034.js:103,12]",
] ]
} }
@@ -151,10 +153,10 @@ non_hoisted_function_after_return_strict: {
} }
expect_stdout: "8 7" expect_stdout: "8 7"
expect_warnings: [ expect_warnings: [
'WARN: Dropping unreachable code [test/compress/issue-1034.js:131,16]', "WARN: Dropping unreachable code [test/compress/issue-1034.js:133,16]",
"WARN: Dropping unreachable code [test/compress/issue-1034.js:134,16]", "WARN: Dropping unreachable code [test/compress/issue-1034.js:136,16]",
"WARN: Dropping unreachable code [test/compress/issue-1034.js:137,12]", "WARN: Dropping unreachable code [test/compress/issue-1034.js:139,12]",
"WARN: Dropping unused function UnusedFunction [test/compress/issue-1034.js:138,21]" "WARN: Dropping unused function UnusedFunction [test/compress/issue-1034.js:140,21]",
] ]
} }
@@ -194,17 +196,19 @@ non_hoisted_function_after_return_2a_strict: {
} }
expect_stdout: "5 6" expect_stdout: "5 6"
expect_warnings: [ expect_warnings: [
"WARN: Dropping unreachable code [test/compress/issue-1034.js:173,16]", "WARN: Dropping unreachable code [test/compress/issue-1034.js:175,16]",
"WARN: Declarations in unreachable code! [test/compress/issue-1034.js:173,16]", "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:175,16]",
"WARN: Dropping unreachable code [test/compress/issue-1034.js:176,16]", "WARN: Dropping unreachable code [test/compress/issue-1034.js:178,16]",
"WARN: Declarations in unreachable code! [test/compress/issue-1034.js:176,16]", "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:178,16]",
"WARN: Dropping unused variable a [test/compress/issue-1034.js:173,20]", "WARN: Dropping unused variable a [test/compress/issue-1034.js:175,20]",
"WARN: Dropping unused function nope [test/compress/issue-1034.js:180,21]", "WARN: Dropping unused function nope [test/compress/issue-1034.js:182,21]",
"WARN: Dropping unreachable code [test/compress/issue-1034.js:178,12]", "WARN: pass 0: last_count: Infinity, count: 48",
"WARN: Declarations in unreachable code! [test/compress/issue-1034.js:178,12]", "WARN: Dropping unreachable code [test/compress/issue-1034.js:180,12]",
"WARN: Dropping unreachable code [test/compress/issue-1034.js:181,12]", "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:180,12]",
"WARN: Dropping unused variable b [test/compress/issue-1034.js:176,20]", "WARN: Dropping unreachable code [test/compress/issue-1034.js:183,12]",
"WARN: Dropping unused variable c [test/compress/issue-1034.js:178,16]", "WARN: Dropping unused variable b [test/compress/issue-1034.js:178,20]",
"WARN: Dropping unused variable c [test/compress/issue-1034.js:180,16]",
"WARN: pass 1: last_count: 48, count: 29",
] ]
} }
@@ -243,10 +247,10 @@ non_hoisted_function_after_return_2b_strict: {
expect_stdout: "5 6" expect_stdout: "5 6"
expect_warnings: [ expect_warnings: [
// duplicate warnings no longer emitted // duplicate warnings no longer emitted
"WARN: Dropping unreachable code [test/compress/issue-1034.js:225,16]", "WARN: Dropping unreachable code [test/compress/issue-1034.js:229,16]",
"WARN: Declarations in unreachable code! [test/compress/issue-1034.js:225,16]", "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:229,16]",
"WARN: Dropping unreachable code [test/compress/issue-1034.js:227,12]",
"WARN: Declarations in unreachable code! [test/compress/issue-1034.js:227,12]",
"WARN: Dropping unreachable code [test/compress/issue-1034.js:231,12]", "WARN: Dropping unreachable code [test/compress/issue-1034.js:231,12]",
"WARN: Declarations in unreachable code! [test/compress/issue-1034.js:231,12]",
"WARN: Dropping unreachable code [test/compress/issue-1034.js:235,12]",
] ]
} }

View File

@@ -1,6 +1,7 @@
typeof_eq_undefined: { typeof_eq_undefined: {
options = { options = {
comparisons: true comparisons: true,
typeofs: true,
} }
input: { input: {
var a = typeof b != "undefined"; var a = typeof b != "undefined";
@@ -24,6 +25,7 @@ typeof_eq_undefined_ie8: {
options = { options = {
comparisons: true, comparisons: true,
ie8: true, ie8: true,
typeofs: true,
} }
input: { input: {
var a = typeof b != "undefined"; var a = typeof b != "undefined";
@@ -45,7 +47,8 @@ typeof_eq_undefined_ie8: {
undefined_redefined: { undefined_redefined: {
options = { options = {
comparisons: true comparisons: true,
typeofs: true,
} }
input: { input: {
function f(undefined) { function f(undefined) {
@@ -58,7 +61,8 @@ undefined_redefined: {
undefined_redefined_mangle: { undefined_redefined_mangle: {
options = { options = {
comparisons: true comparisons: true,
typeofs: true,
} }
mangle = {} mangle = {}
input: { input: {

View File

@@ -419,7 +419,7 @@ wrap_iife_in_return_call: {
expect_exact: '(void console.log("test"))();' expect_exact: '(void console.log("test"))();'
} }
pure_annotation: { pure_annotation_1: {
options = { options = {
inline: true, inline: true,
side_effects: true, side_effects: true,
@@ -432,6 +432,20 @@ pure_annotation: {
expect_exact: "" expect_exact: ""
} }
pure_annotation_2: {
options = {
collapse_vars: true,
inline: true,
side_effects: true,
}
input: {
/*@__PURE__*/(function(n) {
console.log("hello", n);
}(42));
}
expect_exact: ""
}
drop_fargs: { drop_fargs: {
options = { options = {
cascade: true, cascade: true,
@@ -449,9 +463,7 @@ drop_fargs: {
} }
expect: { expect: {
var a = 1; var a = 1;
!function() { ++a && a.var, a++;
a++;
}(++a && a.var);
console.log(a); console.log(a);
} }
expect_stdout: "3" expect_stdout: "3"
@@ -474,9 +486,7 @@ keep_fargs: {
} }
expect: { expect: {
var a = 1; var a = 1;
!function(a_1) { ++a && a.var, a++;
a++;
}(++a && a.var);
console.log(a); console.log(a);
} }
expect_stdout: "3" expect_stdout: "3"

View File

@@ -436,3 +436,17 @@ do_switch: {
} while (false); } while (false);
} }
} }
in_parenthesis_1: {
input: {
for (("foo" in {});0;);
}
expect_exact: 'for(("foo"in{});0;);'
}
in_parenthesis_2: {
input: {
for ((function(){ "foo" in {}; });0;);
}
expect_exact: 'for(function(){"foo"in{}};0;);'
}

View File

@@ -82,3 +82,19 @@ new_with_unary_prefix: {
} }
expect_exact: 'var bar=(+new Date).toString(32);'; expect_exact: 'var bar=(+new Date).toString(32);';
} }
dot_parenthesis_1: {
input: {
console.log(new (Math.random().constructor) instanceof Number);
}
expect_exact: "console.log(new(Math.random().constructor)instanceof Number);"
expect_stdout: "true"
}
dot_parenthesis_2: {
input: {
console.log(typeof new function(){Math.random()}.constructor);
}
expect_exact: "console.log(typeof new function(){Math.random()}.constructor);"
expect_stdout: "function"
}

View File

@@ -13,8 +13,10 @@ keep_properties: {
dot_properties: { dot_properties: {
options = { options = {
properties: true, properties: true,
}
beautify = {
ie8: true, ie8: true,
}; }
input: { input: {
a["foo"] = "bar"; a["foo"] = "bar";
a["if"] = "if"; a["if"] = "if";
@@ -36,8 +38,10 @@ dot_properties: {
dot_properties_es5: { dot_properties_es5: {
options = { options = {
properties: true, properties: true,
}
beautify = {
ie8: false, ie8: false,
}; }
input: { input: {
a["foo"] = "bar"; a["foo"] = "bar";
a["if"] = "if"; a["if"] = "if";
@@ -657,3 +661,116 @@ accessor_this: {
expect_exact: 'var a=1;var b={get this(){return a},set this(c){a=c}};console.log(b.this,b.this=2,b.this);' expect_exact: 'var a=1;var b={get this(){return a},set this(c){a=c}};console.log(b.this,b.this=2,b.this);'
expect_stdout: "1 2 2" expect_stdout: "1 2 2"
} }
issue_2208_1: {
options = {
inline: true,
side_effects: true,
unsafe: true,
}
input: {
console.log({
p: function() {
return 42;
}
}.p());
}
expect: {
console.log(42);
}
expect_stdout: "42"
}
issue_2208_2: {
options = {
inline: true,
side_effects: true,
unsafe: true,
}
input: {
console.log({
a: 42,
p: function() {
return this.a;
}
}.p());
}
expect: {
console.log({
a: 42,
p: function() {
return this.a;
}
}.p());
}
expect_stdout: "42"
}
issue_2208_3: {
options = {
inline: true,
side_effects: true,
unsafe: true,
}
input: {
a = 42;
console.log({
p: function() {
return function() {
return this.a;
}();
}
}.p());
}
expect: {
a = 42;
console.log(function() {
return this.a;
}());
}
expect_stdout: "42"
}
issue_2208_4: {
options = {
inline: true,
side_effects: true,
unsafe: true,
}
input: {
function foo() {}
console.log({
a: foo(),
p: function() {
return 42;
}
}.p());
}
expect: {
function foo() {}
console.log((foo(), function() {
return 42;
})());
}
expect_stdout: "42"
}
issue_2208_5: {
options = {
inline: true,
side_effects: true,
unsafe: true,
}
input: {
console.log({
p: "FAIL",
p: function() {
return 42;
}
}.p());
}
expect: {
console.log(42);
}
expect_stdout: "42"
}

View File

@@ -1469,6 +1469,7 @@ issue_1670_1: {
reduce_vars: true, reduce_vars: true,
side_effects: true, side_effects: true,
switches: true, switches: true,
typeofs: true,
unused: true, unused: true,
} }
input: { input: {
@@ -1532,6 +1533,7 @@ issue_1670_3: {
reduce_vars: true, reduce_vars: true,
side_effects: true, side_effects: true,
switches: true, switches: true,
typeofs: true,
unused: true, unused: true,
} }
input: { input: {

View File

@@ -176,6 +176,11 @@ for_sequences: {
// 4 // 4
x = (foo in bar); x = (foo in bar);
for (y = 5; false;); for (y = 5; false;);
// 5
x = function() {
foo in bar;
};
for (y = 5; false;);
} }
expect: { expect: {
// 1 // 1
@@ -188,6 +193,10 @@ for_sequences: {
// 4 // 4
x = (foo in bar); x = (foo in bar);
for (y = 5; false;); for (y = 5; false;);
// 5
for (x = function() {
foo in bar;
}, y = 5; false;);
} }
} }

View File

@@ -63,7 +63,7 @@ describe("bin/uglifyjs", function () {
if (err) throw err; if (err) throw err;
assert.strictEqual(stdout, "var bar=function(){function foo(bar){return bar}return foo}();\n" + assert.strictEqual(stdout, "var bar=function(){function foo(bar){return bar}return foo}();\n" +
"//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInRlc3QvaW5wdXQvaXNzdWUtMTMyMy9zYW1wbGUuanMiXSwibmFtZXMiOlsiYmFyIiwiZm9vIl0sIm1hcHBpbmdzIjoiQUFBQSxJQUFJQSxJQUFNLFdBQ04sU0FBU0MsSUFBS0QsS0FDVixPQUFPQSxJQUdYLE9BQU9DIn0=\n"); "//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInRlc3QvaW5wdXQvaXNzdWUtMTMyMy9zYW1wbGUuanMiXSwibmFtZXMiOlsiYmFyIiwiZm9vIl0sIm1hcHBpbmdzIjoiQUFBQSxJQUFJQSxJQUFNLFdBQ04sU0FBU0MsSUFBS0QsS0FDVixPQUFPQSxJQUdYLE9BQU9DLElBTEQifQ==\n");
done(); done();
}); });
}); });
@@ -192,7 +192,7 @@ describe("bin/uglifyjs", function () {
assert.strictEqual(stdout, [ assert.strictEqual(stdout, [
"var bar=function(){function foo(bar){return bar}return foo}();", "var bar=function(){function foo(bar){return bar}return foo}();",
"//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInRlc3QvaW5wdXQvaXNzdWUtMTMyMy9zYW1wbGUuanMiXSwibmFtZXMiOlsiYmFyIiwiZm9vIl0sIm1hcHBpbmdzIjoiQUFBQSxJQUFJQSxJQUFNLFdBQ04sU0FBU0MsSUFBS0QsS0FDVixPQUFPQSxJQUdYLE9BQU9DIn0=", "//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInRlc3QvaW5wdXQvaXNzdWUtMTMyMy9zYW1wbGUuanMiXSwibmFtZXMiOlsiYmFyIiwiZm9vIl0sIm1hcHBpbmdzIjoiQUFBQSxJQUFJQSxJQUFNLFdBQ04sU0FBU0MsSUFBS0QsS0FDVixPQUFPQSxJQUdYLE9BQU9DLElBTEQifQ==",
"", "",
].join("\n")); ].join("\n"));
assert.strictEqual(stderr, "WARN: inline source map not found\n"); assert.strictEqual(stderr, "WARN: inline source map not found\n");
@@ -593,8 +593,8 @@ describe("bin/uglifyjs", function () {
done(); done();
}); });
}); });
it("Should work with --mangle reserved=[]", function (done) { it("Should work with --mangle reserved=[]", function(done) {
var command = uglifyjscmd + ' test/input/issue-505/input.js -m reserved=[callback]'; var command = uglifyjscmd + " test/input/issue-505/input.js -m reserved=[callback]";
exec(command, function (err, stdout) { exec(command, function (err, stdout) {
if (err) throw err; if (err) throw err;
@@ -603,8 +603,8 @@ describe("bin/uglifyjs", function () {
done(); done();
}); });
}); });
it("Should work with --mangle reserved=false", function (done) { it("Should work with --mangle reserved=false", function(done) {
var command = uglifyjscmd + ' test/input/issue-505/input.js -m reserved=false'; var command = uglifyjscmd + " test/input/issue-505/input.js -m reserved=false";
exec(command, function (err, stdout) { exec(command, function (err, stdout) {
if (err) throw err; if (err) throw err;
@@ -613,4 +613,22 @@ describe("bin/uglifyjs", function () {
done(); done();
}); });
}); });
it("Should fail with --mangle-props reserved=[in]", function(done) {
var command = uglifyjscmd + " test/input/issue-505/input.js --mangle-props reserved=[in]";
exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.ok(/^Supported options:\n[\s\S]*?\nERROR: `reserved=\[in]` is not a supported option/.test(stderr), stderr);
done();
});
});
it("Should fail with --define a-b", function(done) {
var command = uglifyjscmd + " test/input/issue-505/input.js --define a-b";
exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr, "Error parsing arguments for 'define': a-b\n");
done();
});
});
}); });

View File

@@ -26,12 +26,12 @@ describe("bin/uglifyjs with input file globs", function() {
}); });
}); });
it("bin/uglifyjs with multiple input file globs.", function(done) { it("bin/uglifyjs with multiple input file globs.", function(done) {
var command = uglifyjscmd + ' "test/input/issue-1242/???.es5" "test/input/issue-1242/*.js" -mc toplevel'; var command = uglifyjscmd + ' "test/input/issue-1242/???.es5" "test/input/issue-1242/*.js" -mc toplevel,passes=2';
exec(command, function(err, stdout) { exec(command, function(err, stdout) {
if (err) throw err; if (err) throw err;
assert.strictEqual(stdout, 'var print=console.log.bind(console);print("qux",9,6),print("Foo:",2*11);\n'); assert.strictEqual(stdout, 'var print=console.log.bind(console);print("qux",9,6),print("Foo:",22);\n');
done(); done();
}); });
}); });

View File

@@ -2,16 +2,17 @@ var Uglify = require('../../');
var assert = require("assert"); var assert = require("assert");
describe("let", function() { describe("let", function() {
it("Should not produce reserved keywords as variable name in mangle", function(done) { this.timeout(30000);
this.timeout(10000); it("Should not produce reserved keywords as variable name in mangle", function() {
// Produce a lot of variables in a function and run it through mangle. // Produce a lot of variables in a function and run it through mangle.
var s = '"dddddeeeeelllllooooottttt"; function foo() {'; var s = '"dddddeeeeelllllooooottttt"; function foo() {';
for (var i = 0; i < 18000; i++) { for (var i = 0; i < 18000; i++) {
s += "var v" + i + "=0;"; s += "var v" + i + "=0;";
} }
s += '}'; s += '}';
var result = Uglify.minify(s, {compress: false}); var result = Uglify.minify(s, {
compress: false
}).code;
// Verify that select keywords and reserved keywords not produced // Verify that select keywords and reserved keywords not produced
[ [
@@ -19,7 +20,7 @@ describe("let", function() {
"let", "let",
"var", "var",
].forEach(function(name) { ].forEach(function(name) {
assert.strictEqual(result.code.indexOf("var " + name + "="), -1); assert.strictEqual(result.indexOf("var " + name + "="), -1);
}); });
// Verify that the variable names that appeared immediately before // Verify that the variable names that appeared immediately before
@@ -30,9 +31,27 @@ describe("let", function() {
"eet", "fet", "eet", "fet",
"rar", "oar", "rar", "oar",
].forEach(function(name) { ].forEach(function(name) {
assert.ok(result.code.indexOf("var " + name + "=") >= 0); assert.notStrictEqual(result.indexOf("var " + name + "="), -1);
});
});
it("Should quote mangled properties that are reserved keywords", function() {
var s = '"rrrrrnnnnniiiiiaaaaa";';
for (var i = 0; i < 18000; i++) {
s += "v.b" + i + ";";
}
var result = Uglify.minify(s, {
compress: false,
ie8: true,
mangle: {
properties: true,
}
}).code;
[
"in",
"var",
].forEach(function(name) {
assert.notStrictEqual(result.indexOf(name), -1);
assert.notStrictEqual(result.indexOf('v["' + name + '"]'), -1);
}); });
done();
}); });
}); });

View File

@@ -1,6 +1,7 @@
var Uglify = require('../../'); var Uglify = require('../../');
var assert = require("assert"); var assert = require("assert");
var readFileSync = require("fs").readFileSync; var readFileSync = require("fs").readFileSync;
var run_code = require("../sandbox").run_code;
function read(path) { function read(path) {
return readFileSync(path, "utf8"); return readFileSync(path, "utf8");
@@ -20,6 +21,58 @@ describe("minify", function() {
assert.strictEqual(result.code, "alert(2);"); assert.strictEqual(result.code, "alert(2);");
}); });
it("Should work with mangle.cache", function() {
var cache = {};
var original = "";
var compressed = "";
[
"bar.es5",
"baz.es5",
"foo.es5",
"qux.js",
].forEach(function(file) {
var code = read("test/input/issue-1242/" + file);
var result = Uglify.minify(code, {
mangle: {
cache: cache,
toplevel: true
}
});
if (result.error) throw result.error;
original += code;
compressed += result.code;
});
assert.strictEqual(JSON.stringify(cache).slice(0, 20), '{"cname":5,"props":{');
assert.strictEqual(compressed, 'function n(n){return 3*n}function r(n){return n/2}function c(o){l("Foo:",2*o)}var l=console.log.bind(console);var f=n(3),i=r(12);l("qux",f,i),c(11);');
assert.strictEqual(run_code(compressed), run_code(original));
});
it("Should work with nameCache", function() {
var cache = {};
var original = "";
var compressed = "";
[
"bar.es5",
"baz.es5",
"foo.es5",
"qux.js",
].forEach(function(file) {
var code = read("test/input/issue-1242/" + file);
var result = Uglify.minify(code, {
mangle: {
toplevel: true
},
nameCache: cache
});
if (result.error) throw result.error;
original += code;
compressed += result.code;
});
assert.strictEqual(JSON.stringify(cache).slice(0, 28), '{"vars":{"cname":5,"props":{');
assert.strictEqual(compressed, 'function n(n){return 3*n}function r(n){return n/2}function c(o){l("Foo:",2*o)}var l=console.log.bind(console);var f=n(3),i=r(12);l("qux",f,i),c(11);');
assert.strictEqual(run_code(compressed), run_code(original));
});
describe("keep_quoted_props", function() { describe("keep_quoted_props", function() {
it("Should preserve quotes in object literals", function() { it("Should preserve quotes in object literals", function() {
var js = 'var foo = {"x": 1, y: 2, \'z\': 3};'; var js = 'var foo = {"x": 1, y: 2, \'z\': 3};';