Compare commits

..

17 Commits

Author SHA1 Message Date
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
Alex Lam S.L
5e6f26445f v3.0.21 2017-06-29 00:49:06 +08:00
Alex Lam S.L
f0a99125ee improve unsafe_Func (#2171)
- minimise disturbance to `compute_char_frequency()`
- remove extraneous quotation marks
2017-06-27 23:53:42 +08:00
Alex Lam S.L
1e4de2e6d3 parse @global_defs as expressions (#2169)
- let parser rejects non-conformant input
- eliminate need for extraneous parenthesis
2017-06-27 10:31:19 +08:00
Alex Lam S.L
8b4dcd8f3e v3.0.20 2017-06-25 15:05:05 +08:00
Alex Lam S.L
285401ced8 more tests for #2158 (#2160) 2017-06-25 14:21:48 +08:00
Alex Lam S.L
9db4c42380 fix cascade & collapse on property access of constants (#2158) 2017-06-24 21:30:06 +08:00
Alex Lam S.L
94e5e00c03 refactor compute_char_frequency() (#2152)
- minimise maintenance when updating AST
- maximise code sharing between `master` & `harmony`
2017-06-23 20:05:31 +08:00
Alex Lam S.L
dc6bcaa18e synchronise mangle.properties for minify() & test/compress (#2151) 2017-06-23 15:53:13 +08:00
Alex Lam S.L
d58b184835 refactor Compressor.toplevel (#2149) 2017-06-23 13:11:40 +08:00
Alex Lam S.L
b3a57ff019 minimise reduce_vars cloning overhead (#2148) 2017-06-23 06:59:53 +08:00
Alex Lam S.L
3d5bc08185 fix reduce_vars on this (#2145)
fixes #2140
2017-06-23 04:44:57 +08:00
Alex Lam S.L
0692435f01 fix for-in loop parsing (#2144) 2017-06-23 04:14:30 +08:00
26 changed files with 739 additions and 297 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,
} }

View 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");
@@ -381,18 +370,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

@@ -92,12 +92,9 @@ function Compressor(options, false_by_default) {
var global_defs = this.options["global_defs"]; var global_defs = this.options["global_defs"];
if (typeof global_defs == "object") for (var key in global_defs) { if (typeof global_defs == "object") for (var key in global_defs) {
if (/^@/.test(key) && HOP(global_defs, key)) { if (/^@/.test(key) && HOP(global_defs, key)) {
var ast = parse(global_defs[key]); global_defs[key.slice(1)] = parse(global_defs[key], {
if (ast.body.length == 1 && ast.body[0] instanceof AST_SimpleStatement) { expression: true
global_defs[key.slice(1)] = ast.body[0].body; });
} else throw new Error(string_template("Can't handle expression: {value}", {
value: global_defs[key]
}));
} }
} }
var pure_funcs = this.options["pure_funcs"]; var pure_funcs = this.options["pure_funcs"];
@@ -124,13 +121,13 @@ function Compressor(options, false_by_default) {
}; };
} }
var toplevel = this.options["toplevel"]; var toplevel = this.options["toplevel"];
if (typeof toplevel == "string") { this.toplevel = typeof toplevel == "string" ? {
this.toplevel.funcs = /funcs/.test(toplevel); funcs: /funcs/.test(toplevel),
this.toplevel.vars = /vars/.test(toplevel); vars: /vars/.test(toplevel)
} else { } : {
this.toplevel = toplevel ? return_true : return_false; funcs: toplevel,
this.toplevel.funcs = this.toplevel.vars = toplevel; vars: toplevel
} };
var sequences = this.options["sequences"]; var sequences = this.options["sequences"];
this.sequences_limit = sequences == 1 ? 800 : sequences | 0; this.sequences_limit = sequences == 1 ? 800 : sequences | 0;
this.warnings_produced = {}; this.warnings_produced = {};
@@ -139,11 +136,11 @@ function Compressor(options, false_by_default) {
Compressor.prototype = new TreeTransformer; Compressor.prototype = new TreeTransformer;
merge(Compressor.prototype, { merge(Compressor.prototype, {
option: function(key) { return this.options[key] }, option: function(key) { return this.options[key] },
toplevel: function(def) { exposed: function(def) {
for (var i = 0, len = def.orig.length; i < len; i++) if (def.global) for (var i = 0, len = def.orig.length; i < len; i++)
if (!this.toplevel[def.orig[i] instanceof AST_SymbolDefun ? "funcs" : "vars"]) if (!this.toplevel[def.orig[i] instanceof AST_SymbolDefun ? "funcs" : "vars"])
return false;
return true; return true;
return false;
}, },
compress: function(node) { compress: function(node) {
if (this.option("expression")) { if (this.option("expression")) {
@@ -279,11 +276,11 @@ merge(Compressor.prototype, {
var reduce_vars = rescan && compressor.option("reduce_vars"); var reduce_vars = rescan && compressor.option("reduce_vars");
var safe_ids = Object.create(null); var safe_ids = Object.create(null);
var suppressor = new TreeWalker(function(node) { var suppressor = new TreeWalker(function(node) {
if (node instanceof AST_Symbol) { if (!(node instanceof AST_Symbol)) return;
var d = node.definition(); var d = node.definition();
if (!d) return;
if (node instanceof AST_SymbolRef) d.references.push(node); if (node instanceof AST_SymbolRef) d.references.push(node);
d.fixed = false; d.fixed = false;
}
}); });
var tw = new TreeWalker(function(node, descend) { var tw = new TreeWalker(function(node, descend) {
node._squeezed = false; node._squeezed = false;
@@ -345,7 +342,7 @@ merge(Compressor.prototype, {
} }
if (node instanceof AST_Defun) { if (node instanceof AST_Defun) {
var d = node.name.definition(); var d = node.name.definition();
if (d.global && !compressor.toplevel(d) || safe_to_read(d)) { if (compressor.exposed(d) || safe_to_read(d)) {
d.fixed = false; d.fixed = false;
} else { } else {
d.fixed = node; d.fixed = node;
@@ -517,7 +514,7 @@ merge(Compressor.prototype, {
def.escaped = false; def.escaped = false;
if (def.scope.uses_eval) { if (def.scope.uses_eval) {
def.fixed = false; def.fixed = false;
} else if (!def.global || compressor.toplevel(def)) { } else if (!compressor.exposed(def)) {
def.fixed = undefined; def.fixed = undefined;
} else { } else {
def.fixed = false; def.fixed = false;
@@ -547,8 +544,25 @@ merge(Compressor.prototype, {
return fixed(); return fixed();
}); });
AST_SymbolRef.DEFMETHOD("is_immutable", function() {
var orig = this.definition().orig;
return orig.length == 1 && orig[0] instanceof AST_SymbolLambda;
});
function is_lhs_read_only(lhs) { function is_lhs_read_only(lhs) {
return lhs instanceof AST_SymbolRef && lhs.definition().orig[0] instanceof AST_SymbolLambda; if (lhs instanceof AST_SymbolRef) return lhs.definition().orig[0] instanceof AST_SymbolLambda;
if (lhs instanceof AST_PropAccess) {
lhs = lhs.expression;
if (lhs instanceof AST_SymbolRef) {
if (lhs.is_immutable()) return false;
lhs = lhs.fixed_value();
}
if (!lhs) return true;
if (lhs instanceof AST_RegExp) return false;
if (lhs instanceof AST_Constant) return true;
return is_lhs_read_only(lhs);
}
return false;
} }
function find_variable(compressor, name) { function find_variable(compressor, name) {
@@ -700,15 +714,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
@@ -748,7 +770,7 @@ merge(Compressor.prototype, {
} }
if (candidate instanceof AST_VarDef) { if (candidate instanceof AST_VarDef) {
var def = candidate.name.definition(); var def = candidate.name.definition();
if (def.references.length == 1 && (!def.global || compressor.toplevel(def))) { if (def.references.length == 1 && !compressor.exposed(def)) {
return maintain_this_binding(parent, node, candidate.value); return maintain_this_binding(parent, node, candidate.value);
} }
return make_node(AST_Assign, candidate, { return make_node(AST_Assign, candidate, {
@@ -789,6 +811,35 @@ 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) {
fn.argnames.forEach(function(sym, i) {
var arg = iife.args[i];
if (!arg) arg = make_node(AST_Undefined, sym);
else arg.walk(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 (arg) candidates.push(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 == "--")) {
@@ -809,8 +860,8 @@ 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 && (!def.global || compressor.toplevel(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);
} }
} else { } else {
@@ -851,6 +902,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;
@@ -1287,6 +1346,7 @@ merge(Compressor.prototype, {
def(AST_SymbolRef, function(pure_getters) { def(AST_SymbolRef, function(pure_getters) {
if (this.is_undefined) return true; if (this.is_undefined) return true;
if (!is_strict(pure_getters)) return false; if (!is_strict(pure_getters)) return false;
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._throw_on_access(pure_getters);
}); });
@@ -3105,7 +3165,7 @@ merge(Compressor.prototype, {
// https://github.com/mishoo/UglifyJS2/issues/203 // https://github.com/mishoo/UglifyJS2/issues/203
// if the code argument is a constant, then we can minify it. // if the code argument is a constant, then we can minify it.
try { try {
var code = "NaN(function(" + self.args.slice(0, -1).map(function(arg) { var code = "n(function(" + self.args.slice(0, -1).map(function(arg) {
return arg.value; return arg.value;
}).join(",") + "){" + self.args[self.args.length - 1].value + "})"; }).join(",") + "){" + self.args[self.args.length - 1].value + "})";
var ast = parse(code); var ast = parse(code);
@@ -3114,7 +3174,9 @@ merge(Compressor.prototype, {
var comp = new Compressor(compressor.options); var comp = new Compressor(compressor.options);
ast = ast.transform(comp); ast = ast.transform(comp);
ast.figure_out_scope(mangle); ast.figure_out_scope(mangle);
ast.mangle_names(); base54.reset();
ast.compute_char_frequency(mangle);
ast.mangle_names(mangle);
var fun; var fun;
ast.walk(new TreeWalker(function(node) { ast.walk(new TreeWalker(function(node) {
if (fun) return true; if (fun) return true;
@@ -3123,18 +3185,18 @@ merge(Compressor.prototype, {
return true; return true;
} }
})); }));
var args = fun.argnames.map(function(arg, i) {
return make_node(AST_String, self.args[i], {
value: arg.print_to_string()
});
});
var code = OutputStream(); var code = OutputStream();
AST_BlockStatement.prototype._codegen.call(fun, fun, code); AST_BlockStatement.prototype._codegen.call(fun, fun, code);
code = code.toString().replace(/^\{|\}$/g, ""); self.args = [
args.push(make_node(AST_String, self.args[self.args.length - 1], { make_node(AST_String, self, {
value: code value: fun.argnames.map(function(arg) {
})); return arg.print_to_string();
self.args = args; }).join(",")
}),
make_node(AST_String, self.args[self.args.length - 1], {
value: code.get().replace(/^\{|\}$/g, "")
})
];
return self; return self;
} catch (ex) { } catch (ex) {
if (ex instanceof JS_Parse_Error) { if (ex instanceof JS_Parse_Error) {
@@ -3151,80 +3213,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")) {
@@ -3357,6 +3392,8 @@ merge(Compressor.prototype, {
|| cdr instanceof AST_PropAccess || cdr instanceof AST_PropAccess
|| cdr instanceof AST_Unary && !unary_side_effects(cdr.operator)) { || cdr instanceof AST_Unary && !unary_side_effects(cdr.operator)) {
field = "expression"; field = "expression";
} else if (cdr instanceof AST_Conditional) {
field = "condition";
} else { } else {
expressions[++i] = expressions[j]; expressions[++i] = expressions[j];
break; break;
@@ -3887,7 +3924,7 @@ merge(Compressor.prototype, {
var d = self.definition(); var d = self.definition();
var fixed = self.fixed_value(); var fixed = self.fixed_value();
if (fixed instanceof AST_Defun) { if (fixed instanceof AST_Defun) {
d.fixed = fixed = make_node(AST_Function, fixed, fixed).clone(true); d.fixed = fixed = make_node(AST_Function, fixed, fixed);
} }
if (compressor.option("unused") if (compressor.option("unused")
&& fixed instanceof AST_Function && fixed instanceof AST_Function
@@ -3895,7 +3932,7 @@ merge(Compressor.prototype, {
&& !(d.scope.uses_arguments && d.orig[0] instanceof AST_SymbolFunarg) && !(d.scope.uses_arguments && d.orig[0] instanceof AST_SymbolFunarg)
&& !d.scope.uses_eval && !d.scope.uses_eval
&& compressor.find_parent(AST_Scope) === fixed.parent_scope) { && compressor.find_parent(AST_Scope) === fixed.parent_scope) {
return fixed; return fixed.clone(true);
} }
if (compressor.option("evaluate") && fixed) { if (compressor.option("evaluate") && fixed) {
if (d.should_replace === undefined) { if (d.should_replace === undefined) {
@@ -3918,7 +3955,7 @@ merge(Compressor.prototype, {
} }
var name_length = d.name.length; var name_length = d.name.length;
var overhead = 0; var overhead = 0;
if (compressor.option("unused") && (!d.global || compressor.toplevel(d))) { if (compressor.option("unused") && !compressor.exposed(d)) {
overhead = (name_length + 2 + value_length) / d.references.length; overhead = (name_length + 2 + value_length) / d.references.length;
} }
d.should_replace = value_length <= name_length + overhead ? fn : false; d.should_replace = value_length <= name_length + overhead ? fn : false;

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

@@ -501,6 +501,7 @@ function OutputStream(options) {
use_asm = prev_use_asm; use_asm = prev_use_asm;
} }
}); });
AST_Node.DEFMETHOD("_print", AST_Node.prototype.print);
AST_Node.DEFMETHOD("print_to_string", function(options){ AST_Node.DEFMETHOD("print_to_string", function(options){
var s = OutputStream(options); var s = OutputStream(options);

View File

@@ -1010,8 +1010,12 @@ function parse($TEXT, options) {
? (next(), var_(true)) ? (next(), var_(true))
: expression(true, true); : expression(true, true);
if (is("operator", "in")) { if (is("operator", "in")) {
if (init instanceof AST_Var && init.definitions.length > 1) if (init instanceof AST_Var) {
croak("Only one variable declaration allowed in for..in loop"); if (init.definitions.length > 1)
croak("Only one variable declaration allowed in for..in loop", init.start.line, init.start.col, init.start.pos);
} else if (!is_assignable(init)) {
croak("Invalid left-hand side in for..in loop", init.start.line, init.start.col, init.start.pos);
}
next(); next();
return for_in(init); return for_in(init);
} }

View File

@@ -361,7 +361,8 @@ AST_Function.DEFMETHOD("next_mangled", function(options, def){
}); });
AST_Symbol.DEFMETHOD("unmangleable", function(options){ AST_Symbol.DEFMETHOD("unmangleable", function(options){
return this.definition().unmangleable(options); var def = this.definition();
return !def || def.unmangleable(options);
}); });
// labels are always mangleable // labels are always mangleable
@@ -460,103 +461,69 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options){ AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options){
options = this._default_mangler_options(options); options = this._default_mangler_options(options);
var tw = new TreeWalker(function(node){ try {
if (node instanceof AST_Constant) AST_Node.prototype.print = function(stream, force_parens) {
base54.consider(node.print_to_string()); this._print(stream, force_parens);
else if (node instanceof AST_Return) if (this instanceof AST_Symbol && !this.unmangleable(options)) {
base54.consider("return"); base54.consider(this.name, -1);
else if (node instanceof AST_Throw) } else if (options.properties) {
base54.consider("throw"); if (this instanceof AST_Dot) {
else if (node instanceof AST_Continue) base54.consider(this.property, -1);
base54.consider("continue"); } else if (this instanceof AST_Sub) {
else if (node instanceof AST_Break) skip_string(this.property);
base54.consider("break"); }
else if (node instanceof AST_Debugger) }
base54.consider("debugger"); };
else if (node instanceof AST_Directive) base54.consider(this.print_to_string(), 1);
base54.consider(node.value); } finally {
else if (node instanceof AST_While) AST_Node.prototype.print = AST_Node.prototype._print;
base54.consider("while");
else if (node instanceof AST_Do)
base54.consider("do while");
else if (node instanceof AST_If) {
base54.consider("if");
if (node.alternative) base54.consider("else");
} }
else if (node instanceof AST_Var)
base54.consider("var");
else if (node instanceof AST_Lambda)
base54.consider("function");
else if (node instanceof AST_For)
base54.consider("for");
else if (node instanceof AST_ForIn)
base54.consider("for in");
else if (node instanceof AST_Switch)
base54.consider("switch");
else if (node instanceof AST_Case)
base54.consider("case");
else if (node instanceof AST_Default)
base54.consider("default");
else if (node instanceof AST_With)
base54.consider("with");
else if (node instanceof AST_ObjectSetter)
base54.consider("set" + node.key);
else if (node instanceof AST_ObjectGetter)
base54.consider("get" + node.key);
else if (node instanceof AST_ObjectKeyVal)
base54.consider(node.key);
else if (node instanceof AST_New)
base54.consider("new");
else if (node instanceof AST_This)
base54.consider("this");
else if (node instanceof AST_Try)
base54.consider("try");
else if (node instanceof AST_Catch)
base54.consider("catch");
else if (node instanceof AST_Finally)
base54.consider("finally");
else if (node instanceof AST_Symbol && node.unmangleable(options))
base54.consider(node.name);
else if (node instanceof AST_Unary || node instanceof AST_Binary)
base54.consider(node.operator);
else if (node instanceof AST_Dot)
base54.consider(node.property);
});
this.walk(tw);
base54.sort(); base54.sort();
function skip_string(node) {
if (node instanceof AST_String) {
base54.consider(node.value, -1);
} else if (node instanceof AST_Conditional) {
skip_string(node.consequent);
skip_string(node.alternative);
} else if (node instanceof AST_Sequence) {
skip_string(node.expressions[node.expressions.length - 1]);
}
}
}); });
var base54 = (function() { var base54 = (function() {
var string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_0123456789"; var leading = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_".split("");
var digits = "0123456789".split("");
var chars, frequency; var chars, frequency;
function reset() { function reset() {
frequency = Object.create(null); frequency = Object.create(null);
chars = string.split("").map(function(ch){ return ch.charCodeAt(0) }); leading.forEach(function(ch) {
chars.forEach(function(ch){ frequency[ch] = 0 }); frequency[ch] = 0;
});
digits.forEach(function(ch) {
frequency[ch] = 0;
});
} }
base54.consider = function(str){ base54.consider = function(str, delta) {
for (var i = str.length; --i >= 0;) { for (var i = str.length; --i >= 0;) {
var code = str.charCodeAt(i); frequency[str[i]] += delta;
if (code in frequency) ++frequency[code];
} }
}; };
base54.sort = function() { function compare(a, b) {
chars = mergeSort(chars, function(a, b){
if (is_digit(a) && !is_digit(b)) return 1;
if (is_digit(b) && !is_digit(a)) return -1;
return frequency[b] - frequency[a]; return frequency[b] - frequency[a];
}); }
base54.sort = function() {
chars = mergeSort(leading, compare).concat(mergeSort(digits, compare));
}; };
base54.reset = reset; base54.reset = reset;
reset(); reset();
base54.get = function(){ return chars };
base54.freq = function(){ return frequency };
function base54(num) { function base54(num) {
var ret = "", base = 54; var ret = "", base = 54;
num++; num++;
do { do {
num--; num--;
ret += String.fromCharCode(chars[num % base]); ret += chars[num % base];
num = Math.floor(num / base); num = Math.floor(num / base);
base = 64; base = 64;
} while (num > 0); } while (num > 0);

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.19", "version": "3.0.23",
"engines": { "engines": {
"node": ">=0.8.0" "node": ">=0.8.0"
}, },

View File

@@ -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,73 @@ 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"
}

View File

@@ -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,7 @@ issue_2105: {
quux(); quux();
} }
}; };
}().prop(); })().prop();
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }

View File

@@ -265,7 +265,7 @@ issue_203: {
} }
expect: { expect: {
var m = {}; var m = {};
var fn = Function("a", "b", "b.exports=42"); var fn = Function("n,o", "o.exports=42");
fn(null, m, m.exports); fn(null, m, m.exports);
console.log(m.exports); console.log(m.exports);
} }
@@ -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

@@ -174,3 +174,24 @@ issue_1986: {
console.log(42); console.log(42);
} }
} }
issue_2167: {
options = {
conditionals: true,
dead_code: true,
evaluate: true,
global_defs: {
"@isDevMode": "function(){}",
},
side_effects: true,
}
input: {
if (isDevMode()) {
greetOverlord();
}
doWork();
}
expect: {
doWork();
}
}

View File

@@ -10,9 +10,9 @@ issue_1321_no_debug: {
} }
expect: { expect: {
var x = {}; var x = {};
x.b = 1; x.o = 1;
x["a"] = 2 * x.b; x["a"] = 2 * x.o;
console.log(x.b, x["a"]); console.log(x.o, x["a"]);
} }
expect_stdout: true expect_stdout: true
} }
@@ -30,9 +30,9 @@ issue_1321_debug: {
} }
expect: { expect: {
var x = {}; var x = {};
x.a = 1; x.o = 1;
x["_$foo$_"] = 2 * x.a; x["_$foo$_"] = 2 * x.o;
console.log(x.a, x["_$foo$_"]); console.log(x.o, x["_$foo$_"]);
} }
expect_stdout: true expect_stdout: true
} }
@@ -49,9 +49,9 @@ issue_1321_with_quoted: {
} }
expect: { expect: {
var x = {}; var x = {};
x.a = 1; x.o = 1;
x["b"] = 2 * x.a; x["x"] = 2 * x.o;
console.log(x.a, x["b"]); console.log(x.o, x["x"]);
} }
expect_stdout: true expect_stdout: true
} }

View File

@@ -82,7 +82,7 @@ numeric_literal: {
' 42: 2,', ' 42: 2,',
' "42": 3,', ' "42": 3,',
' 37: 4,', ' 37: 4,',
' a: 5,', ' o: 5,',
' 1e42: 6,', ' 1e42: 6,',
' b: 7,', ' b: 7,',
' "1e+42": 8', ' "1e+42": 8',
@@ -92,7 +92,7 @@ numeric_literal: {
'', '',
'console.log(obj[42], obj["42"]);', 'console.log(obj[42], obj["42"]);',
'', '',
'console.log(obj[37], obj["a"], obj[37], obj["37"]);', 'console.log(obj[37], obj["o"], obj[37], obj["37"]);',
'', '',
'console.log(obj[1e42], obj["b"], obj["1e+42"]);', 'console.log(obj[1e42], obj["b"], obj["1e+42"]);',
] ]
@@ -173,32 +173,32 @@ identifier: {
} }
expect: { expect: {
var obj = { var obj = {
a: 1, e: 1,
b: 2, t: 2,
c: 3, n: 3,
d: 4, a: 4,
e: 5, i: 5,
f: 6, o: 6,
g: 7, r: 7,
h: 8, l: 8,
i: 9, s: 9,
j: 10, c: 10,
k: 11, f: 11,
l: 12, u: 12,
m: 13, d: 13,
n: 14, h: 14,
o: 15, p: 15,
p: 16, b: 16,
q: 17, v: 17,
r: 18, w: 18,
s: 19, y: 19,
t: 20, g: 20,
u: 21, m: 21,
v: 22, k: 22,
w: 23, x: 23,
x: 24, j: 24,
y: 25, z: 25,
z: 26, q: 26,
A: 27, A: 27,
B: 28, B: 28,
C: 29, C: 29,
@@ -229,11 +229,11 @@ identifier: {
Z: 54, Z: 54,
$: 55, $: 55,
_: 56, _: 56,
aa: 57, ee: 57,
ba: 58, te: 58,
ca: 59, ne: 59,
da: 60, ae: 60,
ea: 61, ie: 61,
}; };
} }
} }

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

@@ -1,37 +1,41 @@
dont_reuse_prop: { dont_reuse_prop: {
mangle_props = { mangle_props = {
regex: /asd/ regex: /asd/
}; }
input: { input: {
"aaaaaaaaaabbbbb";
var obj = {}; var obj = {};
obj.a = 123; obj.a = 123;
obj.asd = 256; obj.asd = 256;
console.log(obj.a); console.log(obj.a);
} }
expect: { expect: {
"aaaaaaaaaabbbbb";
var obj = {}; var obj = {};
obj.a = 123; obj.a = 123;
obj.b = 256; obj.b = 256;
console.log(obj.a); console.log(obj.a);
} }
expect_stdout: "123"
} }
unmangleable_props_should_always_be_reserved: { unmangleable_props_should_always_be_reserved: {
mangle_props = { mangle_props = {
regex: /asd/ regex: /asd/
}; }
input: { input: {
"aaaaaaaaaabbbbb";
var obj = {}; var obj = {};
obj.asd = 256; obj.asd = 256;
obj.a = 123; obj.a = 123;
console.log(obj.a); console.log(obj.a);
} }
expect: { expect: {
"aaaaaaaaaabbbbb";
var obj = {}; var obj = {};
obj.b = 256; obj.b = 256;
obj.a = 123; obj.a = 123;
console.log(obj.a); console.log(obj.a);
} }
expect_stdout: "123"
} }

View File

@@ -135,11 +135,11 @@ mangle_properties: {
a['run']({color: "blue", foo: "baz"}); a['run']({color: "blue", foo: "baz"});
} }
expect: { expect: {
a["a"] = "bar"; a["o"] = "bar";
a.b = "red"; a.a = "red";
x = {c: 10}; x = {r: 10};
a.d(x.c, a.a); a.b(x.r, a.o);
a['d']({b: "blue", a: "baz"}); a['b']({a: "blue", o: "baz"});
} }
} }
@@ -177,16 +177,16 @@ mangle_unquoted_properties: {
function f1() { function f1() {
a["foo"] = "bar"; a["foo"] = "bar";
a.color = "red"; a.color = "red";
a.b = 2; a.o = 2;
x = {"bar": 10, c: 7}; x = {"bar": 10, f: 7};
a.c = 9; a.f = 9;
} }
function f2() { function f2() {
a.foo = "bar"; a.foo = "bar";
a['color'] = "red"; a['color'] = "red";
x = {bar: 10, c: 7}; x = {bar: 10, f: 7};
a.c = 9; a.f = 9;
a.b = 3; a.o = 3;
} }
} }
} }

View File

@@ -241,3 +241,147 @@ issue_2110_2: {
} }
expect_stdout: "function" expect_stdout: "function"
} }
set_immutable_1: {
options = {
collapse_vars: true,
evaluate: true,
pure_getters: "strict",
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var a = 1;
a.foo += "";
if (a.foo) console.log("FAIL");
else console.log("PASS");
}
expect: {
1..foo += "";
if (1..foo) console.log("FAIL");
else console.log("PASS");
}
expect_stdout: "PASS"
}
set_immutable_2: {
options = {
cascade: true,
conditionals: true,
pure_getters: "strict",
reduce_vars: true,
sequences: true,
side_effects: true,
toplevel: true,
}
input: {
var a = 1;
a.foo += "";
if (a.foo) console.log("FAIL");
else console.log("PASS");
}
expect: {
var a = 1;
a.foo += "", a.foo ? console.log("FAIL") : console.log("PASS");
}
expect_stdout: "PASS"
}
set_immutable_3: {
options = {
collapse_vars: true,
evaluate: true,
pure_getters: "strict",
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
"use strict";
var a = 1;
a.foo += "";
if (a.foo) console.log("FAIL");
else console.log("PASS");
}
expect: {
"use strict";
1..foo += "";
if (1..foo) console.log("FAIL");
else console.log("PASS");
}
expect_stdout: true
}
set_immutable_4: {
options = {
cascade: true,
conditionals: true,
pure_getters: "strict",
reduce_vars: true,
sequences: true,
side_effects: true,
toplevel: true,
}
input: {
"use strict";
var a = 1;
a.foo += "";
if (a.foo) console.log("FAIL");
else console.log("PASS");
}
expect: {
"use strict";
var a = 1;
a.foo += "", a.foo ? console.log("FAIL") : console.log("PASS");
}
expect_stdout: true
}
set_mutable_1: {
options = {
collapse_vars: true,
evaluate: true,
pure_getters: "strict",
reduce_vars: true,
unused: true,
}
input: {
!function a() {
a.foo += "";
if (a.foo) console.log("PASS");
else console.log("FAIL");
}();
}
expect: {
!function a() {
if (a.foo += "") console.log("PASS");
else console.log("FAIL");
}();
}
expect_stdout: "PASS"
}
set_mutable_2: {
options = {
cascade: true,
conditionals: true,
pure_getters: "strict",
reduce_vars: true,
sequences: true,
side_effects: true,
}
input: {
!function a() {
a.foo += "";
if (a.foo) console.log("PASS");
else console.log("FAIL");
}();
}
expect: {
!function a() {
(a.foo += "") ? console.log("PASS") : console.log("FAIL");
}();
}
expect_stdout: "PASS"
}

View File

@@ -2575,3 +2575,28 @@ accessor: {
} }
expect_stdout: "1 1" expect_stdout: "1 1"
} }
for_in_prop: {
options = {
reduce_vars: true,
}
input: {
var a = {
foo: function() {
for (this.b in [1, 2]);
}
};
a.foo();
console.log(a.b);
}
expect: {
var a = {
foo: function() {
for (this.b in [1, 2]);
}
};
a.foo();
console.log(a.b);
}
expect_stdout: "1"
}

View File

@@ -0,0 +1,4 @@
var a, b = [1, 2];
for (1, 2, a in b) {
console.log(a, b[a]);
}

View File

@@ -0,0 +1,4 @@
var c = [1, 2];
for (var a, b in c) {
console.log(a, c[a]);
}

View File

@@ -518,6 +518,36 @@ describe("bin/uglifyjs", function () {
done(); done();
}); });
}); });
it("Should throw syntax error (for-in init)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/for-in_1.js';
exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/for-in_1.js:2,5",
"for (1, 2, a in b) {",
" ^",
"ERROR: Invalid left-hand side in for..in loop"
].join("\n"));
done();
});
});
it("Should throw syntax error (for-in var)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/for-in_2.js';
exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/for-in_2.js:2,5",
"for (var a, b in c) {",
" ^",
"ERROR: Only one variable declaration allowed in for..in loop"
].join("\n"));
done();
});
});
it("Should handle literal string as source map input", function(done) { it("Should handle literal string as source map input", function(done) {
var command = [ var command = [
uglifyjscmd, uglifyjscmd,

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,29 +2,37 @@ var Uglify = require('../../');
var assert = require("assert"); var assert = require("assert");
describe("let", function() { describe("let", function() {
it("Should not produce `let` as a variable name in mangle", function(done) { it("Should not produce reserved keywords as variable name in mangle", function(done) {
this.timeout(10000); this.timeout(10000);
// 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 = '"use strict"; function foo() {'; var s = '"dddddeeeeelllllooooottttt"; function foo() {';
for (var i = 0; i < 21000; ++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});
// Verify that select keywords and reserved keywords not produced // Verify that select keywords and reserved keywords not produced
assert.strictEqual(result.code.indexOf("var let="), -1); [
assert.strictEqual(result.code.indexOf("var do="), -1); "do",
assert.strictEqual(result.code.indexOf("var var="), -1); "let",
"var",
].forEach(function(name) {
assert.strictEqual(result.code.indexOf("var " + name + "="), -1);
});
// Verify that the variable names that appeared immediately before // Verify that the variable names that appeared immediately before
// and after the erroneously generated `let` variable name still exist // and after the erroneously generated variable name still exist
// to show the test generated enough symbols. // to show the test generated enough symbols.
assert(result.code.indexOf("var ket=") >= 0); [
assert(result.code.indexOf("var met=") >= 0); "to", "eo",
"eet", "fet",
"rar", "oar",
].forEach(function(name) {
assert.ok(result.code.indexOf("var " + name + "=") >= 0);
});
done(); 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};';
@@ -212,7 +265,7 @@ describe("minify", function() {
}); });
var err = result.error; var err = result.error;
assert.ok(err instanceof Error); assert.ok(err instanceof Error);
assert.strictEqual(err.stack.split(/\n/)[0], "Error: Can't handle expression: debugger"); assert.strictEqual(err.stack.split(/\n/)[0], "SyntaxError: Unexpected token: keyword (debugger)");
}); });
it("should skip inherited properties", function() { it("should skip inherited properties", function() {
var foo = Object.create({ skip: this }); var foo = Object.create({ skip: this });

View File

@@ -86,7 +86,6 @@ function run_compress_tests() {
log_start_file(file); log_start_file(file);
function test_case(test) { function test_case(test) {
log_test(test.name); log_test(test.name);
U.base54.reset();
var output_options = test.beautify || {}; var output_options = test.beautify || {};
var expect; var expect;
if (test.expect) { if (test.expect) {
@@ -101,9 +100,6 @@ function run_compress_tests() {
quote_style: 3, quote_style: 3,
keep_quoted_props: true keep_quoted_props: true
}); });
if (test.mangle_props) {
input = U.mangle_properties(input, test.mangle_props);
}
var options = U.defaults(test.options, { var options = U.defaults(test.options, {
warnings: false warnings: false
}); });
@@ -118,10 +114,16 @@ function run_compress_tests() {
var cmp = new U.Compressor(options, true); var cmp = new U.Compressor(options, true);
var output = cmp.compress(input); var output = cmp.compress(input);
output.figure_out_scope(test.mangle); output.figure_out_scope(test.mangle);
if (test.mangle) { if (test.mangle || test.mangle_props) {
U.base54.reset();
output.compute_char_frequency(test.mangle); output.compute_char_frequency(test.mangle);
}
if (test.mangle) {
output.mangle_names(test.mangle); output.mangle_names(test.mangle);
} }
if (test.mangle_props) {
output = U.mangle_properties(output, test.mangle_props);
}
output = make_code(output, output_options); output = make_code(output, output_options);
if (expect != output) { if (expect != output) {
log("!!! failed\n---INPUT---\n{input}\n---OUTPUT---\n{output}\n---EXPECTED---\n{expected}\n\n", { log("!!! failed\n---INPUT---\n{input}\n---OUTPUT---\n{output}\n---EXPECTED---\n{expected}\n\n", {