Merge branch 'master' into harmony

This commit is contained in:
Richard van Velzen
2016-04-17 13:37:01 +02:00
36 changed files with 2805 additions and 155 deletions

View File

@@ -125,7 +125,9 @@ The available options are:
--noerr Don't throw an error for unknown options in -c, --noerr Don't throw an error for unknown options in -c,
-b or -m. -b or -m.
--bare-returns Allow return outside of functions. Useful when --bare-returns Allow return outside of functions. Useful when
minifying CommonJS modules. minifying CommonJS modules and Userscripts that
may be anonymous function wrapped (IIFE) by the
.user.js engine `caller`.
--keep-fnames Do not mangle/drop function names. Useful for --keep-fnames Do not mangle/drop function names. Useful for
code relying on Function.prototype.name. code relying on Function.prototype.name.
--reserved-file File containing reserved names --reserved-file File containing reserved names
@@ -190,11 +192,6 @@ input files from the command line.
To enable the mangler you need to pass `--mangle` (`-m`). The following To enable the mangler you need to pass `--mangle` (`-m`). The following
(comma-separated) options are supported: (comma-separated) options are supported:
- `sort` — to assign shorter names to most frequently used variables. This
saves a few hundred bytes on jQuery before gzip, but the output is
_bigger_ after gzip (and seems to happen for other libraries I tried it
on) therefore it's not enabled by default.
- `toplevel` — mangle names declared in the toplevel scope (disabled by - `toplevel` — mangle names declared in the toplevel scope (disabled by
default). default).
@@ -323,6 +320,9 @@ to set `true`; it's effectively a shortcut for `foo=true`).
- `cascade` -- small optimization for sequences, transform `x, x` into `x` - `cascade` -- small optimization for sequences, transform `x, x` into `x`
and `x = something(), x` into `x = something()` and `x = something(), x` into `x = something()`
- `collapse_vars` -- default `false`. Collapse single-use `var` and `const`
definitions when possible.
- `warnings` -- display warnings when dropping unreachable code or unused - `warnings` -- display warnings when dropping unreachable code or unused
declarations etc. declarations etc.
@@ -395,6 +395,8 @@ separate file and include it into the build. For example you can have a
```javascript ```javascript
const DEBUG = false; const DEBUG = false;
const PRODUCTION = true; const PRODUCTION = true;
// Alternative for environments that don't support `const`
/** @const */ var STAGING = false;
// etc. // etc.
``` ```
@@ -404,10 +406,26 @@ and build your code like this:
UglifyJS will notice the constants and, since they cannot be altered, it UglifyJS will notice the constants and, since they cannot be altered, it
will evaluate references to them to the value itself and drop unreachable will evaluate references to them to the value itself and drop unreachable
code as usual. The possible downside of this approach is that the build code as usual. The build will contain the `const` declarations if you use
will contain the `const` declarations. them. If you are targeting < ES6 environments, use `/** @const */ var`.
<a name="codegen-options"></a> <a name="codegen-options"></a>
#### Conditional compilation, API
You can also use conditional compilation via the programmatic API. With the difference that the
property name is `global_defs` and is a compressor property:
```js
uglifyJS.minify([ "input.js"], {
compress: {
dead_code: true,
global_defs: {
DEBUG: false
}
}
});
```
## Beautifier options ## Beautifier options
The code generator tries to output shortest code possible by default. In The code generator tries to output shortest code possible by default. In
@@ -624,6 +642,9 @@ Other options:
- `mangle` — pass `false` to skip mangling names. - `mangle` — pass `false` to skip mangling names.
- `mangleProperties` (default `false`) — pass an object to specify custom
mangle property options.
- `output` (default `null`) — pass an object if you wish to specify - `output` (default `null`) — pass an object if you wish to specify
additional [output options][codegen]. The defaults are optimized additional [output options][codegen]. The defaults are optimized
for best compression. for best compression.
@@ -631,6 +652,13 @@ Other options:
- `compress` (default `{}`) — pass `false` to skip compressing entirely. - `compress` (default `{}`) — pass `false` to skip compressing entirely.
Pass an object to specify custom [compressor options][compressor]. Pass an object to specify custom [compressor options][compressor].
- `parse` (default {}) — pass an object if you wish to specify some
additional [parser options][parser]. (not all options available... see below)
##### mangleProperties options
- `regex` — Pass a RegExp to only mangle certain names (maps to the `--mange-regex` CLI arguments option)
We could add more options to `UglifyJS.minify` — if you need additional We could add more options to `UglifyJS.minify` — if you need additional
functionality please suggest! functionality please suggest!
@@ -649,6 +677,9 @@ properties are available:
- `strict` — disable automatic semicolon insertion and support for trailing - `strict` — disable automatic semicolon insertion and support for trailing
comma in arrays and objects comma in arrays and objects
- `bare_returns` — Allow return outside of functions. (maps to the
`--bare-returns` CLI arguments option and available to `minify` `parse`
other options object)
- `filename` — the name of the file where this code is coming from - `filename` — the name of the file where this code is coming from
- `toplevel` — a `toplevel` node (as returned by a previous invocation of - `toplevel` — a `toplevel` node (as returned by a previous invocation of
`parse`) `parse`)
@@ -788,3 +819,4 @@ The `source_map_options` (optional) can contain the following properties:
[sm-spec]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit [sm-spec]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit
[codegen]: http://lisperator.net/uglifyjs/codegen [codegen]: http://lisperator.net/uglifyjs/codegen
[compressor]: http://lisperator.net/uglifyjs/compress [compressor]: http://lisperator.net/uglifyjs/compress
[parser]: http://lisperator.net/uglifyjs/parser

View File

@@ -499,17 +499,19 @@ function normalize(o) {
} }
} }
function getOptions(x, constants) { function getOptions(flag, constants) {
x = ARGS[x]; var x = ARGS[flag];
if (x == null) return null; if (x == null || x === false) return null;
var ret = {}; var ret = {};
if (x !== "") { if (x !== "") {
if (Array.isArray(x)) x = x.map(function (v) { return "(" + v + ")"; }).join(", ");
var ast; var ast;
try { try {
ast = UglifyJS.parse(x, { expression: true }); ast = UglifyJS.parse(x, { expression: true });
} catch(ex) { } catch(ex) {
if (ex instanceof UglifyJS.JS_Parse_Error) { if (ex instanceof UglifyJS.JS_Parse_Error) {
print_error("Error parsing arguments in: " + x); print_error("Error parsing arguments for flag `" + flag + "': " + x);
process.exit(1); process.exit(1);
} }
} }
@@ -529,7 +531,7 @@ function getOptions(x, constants) {
return true; // no descend return true; // no descend
} }
print_error(node.TYPE) print_error(node.TYPE)
print_error("Error parsing arguments in: " + x); print_error("Error parsing arguments for flag `" + flag + "': " + x);
process.exit(1); process.exit(1);
})); }));
} }

View File

@@ -71,7 +71,7 @@ function DEFNODE(type, props, methods, base) {
if (type) { if (type) {
ctor.prototype.TYPE = ctor.TYPE = type; ctor.prototype.TYPE = ctor.TYPE = type;
} }
if (methods) for (i in methods) if (methods.hasOwnProperty(i)) { if (methods) for (i in methods) if (HOP(methods, i)) {
if (/^\$/.test(i)) { if (/^\$/.test(i)) {
ctor[i.substr(1)] = methods[i]; ctor[i.substr(1)] = methods[i];
} else { } else {
@@ -294,7 +294,7 @@ var AST_Scope = DEFNODE("Scope", "is_block_scope directives variables functions
parent_scope: "[AST_Scope?/S] link to the parent scope", parent_scope: "[AST_Scope?/S] link to the parent scope",
enclosed: "[SymbolDef*/S] a list of all symbol definitions that are accessed from this scope or any subscopes", enclosed: "[SymbolDef*/S] a list of all symbol definitions that are accessed from this scope or any subscopes",
cname: "[integer/S] current index for mangling variables (used internally by the mangler)", cname: "[integer/S] current index for mangling variables (used internally by the mangler)",
}, }
}, AST_Block); }, AST_Block);
var AST_Toplevel = DEFNODE("Toplevel", "globals", { var AST_Toplevel = DEFNODE("Toplevel", "globals", {
@@ -849,6 +849,13 @@ var AST_Seq = DEFNODE("Seq", "car cdr", {
p = p.cdr; p = p.cdr;
} }
}, },
len: function() {
if (this.cdr instanceof AST_Seq) {
return this.cdr.len() + 1;
} else {
return 2;
}
},
_walk: function(visitor) { _walk: function(visitor) {
return visitor._visit(this, function(){ return visitor._visit(this, function(){
this.car._walk(visitor); this.car._walk(visitor);

View File

@@ -66,6 +66,7 @@ function Compressor(options, false_by_default) {
hoist_vars : false, hoist_vars : false,
if_return : !false_by_default, if_return : !false_by_default,
join_vars : !false_by_default, join_vars : !false_by_default,
collapse_vars : false,
cascade : !false_by_default, cascade : !false_by_default,
side_effects : !false_by_default, side_effects : !false_by_default,
pure_getters : false, pure_getters : false,
@@ -175,6 +176,23 @@ merge(Compressor.prototype, {
} }
}; };
// we shouldn't compress (1,func)(something) to
// func(something) because that changes the meaning of
// the func (becomes lexical instead of global).
function maintain_this_binding(parent, orig, val) {
if (parent instanceof AST_Call && parent.expression === orig) {
if (val instanceof AST_PropAccess || val instanceof AST_SymbolRef && val.name === "eval") {
return make_node(AST_Seq, orig, {
car: make_node(AST_Number, orig, {
value: 0
}),
cdr: val
});
}
}
return val;
}
function as_statement_array(thing) { function as_statement_array(thing) {
if (thing === null) return []; if (thing === null) return [];
if (thing instanceof AST_BlockStatement) return thing.body; if (thing instanceof AST_BlockStatement) return thing.body;
@@ -226,6 +244,9 @@ merge(Compressor.prototype, {
if (compressor.option("join_vars")) { if (compressor.option("join_vars")) {
statements = join_consecutive_vars(statements, compressor); statements = join_consecutive_vars(statements, compressor);
} }
if (compressor.option("collapse_vars")) {
statements = collapse_single_use_vars(statements, compressor);
}
} while (CHANGED && max_iter-- > 0); } while (CHANGED && max_iter-- > 0);
if (compressor.option("negate_iife")) { if (compressor.option("negate_iife")) {
@@ -234,6 +255,163 @@ merge(Compressor.prototype, {
return statements; return statements;
function collapse_single_use_vars(statements, compressor) {
// Iterate statements backwards looking for a statement with a var/const
// declaration immediately preceding it. Grab the rightmost var definition
// and if it has exactly one reference then attempt to replace its reference
// in the statement with the var value and then erase the var definition.
var self = compressor.self();
var var_defs_removed = false;
for (var stat_index = statements.length; --stat_index >= 0;) {
var stat = statements[stat_index];
if (stat instanceof AST_Definitions) continue;
// Process child blocks of statement if present.
[stat, stat.body, stat.alternative, stat.bcatch, stat.bfinally].forEach(function(node) {
node && node.body && collapse_single_use_vars(node.body, compressor);
});
// The variable definition must precede a statement.
if (stat_index <= 0) break;
var prev_stat_index = stat_index - 1;
var prev_stat = statements[prev_stat_index];
if (!(prev_stat instanceof AST_Definitions)) continue;
var var_defs = prev_stat.definitions;
if (var_defs == null) continue;
var var_names_seen = {};
var side_effects_encountered = false;
var lvalues_encountered = false;
var lvalues = {};
// Scan variable definitions from right to left.
for (var var_defs_index = var_defs.length; --var_defs_index >= 0;) {
// Obtain var declaration and var name with basic sanity check.
var var_decl = var_defs[var_defs_index];
if (var_decl.value == null) break;
var var_name = var_decl.name.name;
if (!var_name || !var_name.length) break;
// Bail if we've seen a var definition of same name before.
if (var_name in var_names_seen) break;
var_names_seen[var_name] = true;
// Only interested in cases with just one reference to the variable.
var def = self.find_variable && self.find_variable(var_name);
if (!def || !def.references || def.references.length !== 1 || var_name == "arguments") {
side_effects_encountered = true;
continue;
}
var ref = def.references[0];
// Don't replace ref if eval() or with statement in scope.
if (ref.scope.uses_eval || ref.scope.uses_with) break;
// Constant single use vars can be replaced in any scope.
if (var_decl.value.is_constant(compressor)) {
var ctt = new TreeTransformer(function(node) {
if (node === ref)
return replace_var(node, ctt.parent(), true);
});
stat.transform(ctt);
continue;
}
// Restrict var replacement to constants if side effects encountered.
if (side_effects_encountered |= lvalues_encountered) continue;
// Non-constant single use vars can only be replaced in same scope.
if (ref.scope !== self) {
side_effects_encountered |= var_decl.value.has_side_effects(compressor);
continue;
}
// Detect lvalues in var value.
var tw = new TreeWalker(function(node){
if (node instanceof AST_SymbolRef && is_lvalue(node, tw.parent())) {
lvalues[node.name] = lvalues_encountered = true;
}
});
var_decl.value.walk(tw);
// Replace the non-constant single use var in statement if side effect free.
var unwind = false;
var tt = new TreeTransformer(
function preorder(node) {
if (unwind) return node;
var parent = tt.parent();
if (node instanceof AST_Lambda
|| node instanceof AST_Try
|| node instanceof AST_With
|| node instanceof AST_Case
|| node instanceof AST_IterationStatement
|| (parent instanceof AST_If && node !== parent.condition)
|| (parent instanceof AST_Conditional && node !== parent.condition)
|| (parent instanceof AST_Binary
&& (parent.operator == "&&" || parent.operator == "||")
&& node === parent.right)
|| (parent instanceof AST_Switch && node !== parent.expression)) {
return side_effects_encountered = unwind = true, node;
}
},
function postorder(node) {
if (unwind) return node;
if (node === ref)
return unwind = true, replace_var(node, tt.parent(), false);
if (side_effects_encountered |= node.has_side_effects(compressor))
return unwind = true, node;
if (lvalues_encountered && node instanceof AST_SymbolRef && node.name in lvalues) {
side_effects_encountered = true;
return unwind = true, node;
}
}
);
stat.transform(tt);
}
}
// Remove extraneous empty statments in block after removing var definitions.
// Leave at least one statement in `statements`.
if (var_defs_removed) for (var i = statements.length; --i >= 0;) {
if (statements.length > 1 && statements[i] instanceof AST_EmptyStatement)
statements.splice(i, 1);
}
return statements;
function is_lvalue(node, parent) {
return node instanceof AST_SymbolRef && (
(parent instanceof AST_Assign && node === parent.left)
|| (parent instanceof AST_Unary && parent.expression === node
&& (parent.operator == "++" || parent.operator == "--")));
}
function replace_var(node, parent, is_constant) {
if (is_lvalue(node, parent)) return node;
// Remove var definition and return its value to the TreeTransformer to replace.
var value = maintain_this_binding(parent, node, var_decl.value);
var_decl.value = null;
var_defs.splice(var_defs_index, 1);
if (var_defs.length === 0) {
statements[prev_stat_index] = make_node(AST_EmptyStatement, self);
var_defs_removed = true;
}
// Further optimize statement after substitution.
stat.walk(new TreeWalker(function(node){
delete node._squeezed;
delete node._optimized;
}));
compressor.warn("Replacing " + (is_constant ? "constant" : "variable") +
" " + var_name + " [{file}:{line},{col}]", node.start);
CHANGED = true;
return value;
}
}
function process_for_angular(statements) { function process_for_angular(statements) {
function has_inject(comment) { function has_inject(comment) {
return /@ngInject/.test(comment.value); return /@ngInject/.test(comment.value);
@@ -511,8 +689,12 @@ merge(Compressor.prototype, {
seq = []; seq = [];
}; };
statements.forEach(function(stat){ statements.forEach(function(stat){
if (stat instanceof AST_SimpleStatement && seq.length < 2000) seq.push(stat.body); if (stat instanceof AST_SimpleStatement && seqLength(seq) < 2000) {
else push_seq(), ret.push(stat); seq.push(stat.body);
} else {
push_seq();
ret.push(stat);
}
}); });
push_seq(); push_seq();
ret = sequencesize_2(ret, compressor); ret = sequencesize_2(ret, compressor);
@@ -520,6 +702,18 @@ merge(Compressor.prototype, {
return ret; return ret;
}; };
function seqLength(a) {
for (var len = 0, i = 0; i < a.length; ++i) {
var stat = a[i];
if (stat instanceof AST_Seq) {
len += stat.len();
} else {
len++;
}
}
return len;
};
function sequencesize_2(statements, compressor) { function sequencesize_2(statements, compressor) {
function cons_seq(right) { function cons_seq(right) {
ret.pop(); ret.pop();
@@ -639,7 +833,9 @@ merge(Compressor.prototype, {
}; };
function extract_declarations_from_unreachable_code(compressor, stat, target) { function extract_declarations_from_unreachable_code(compressor, stat, target) {
if (!(stat instanceof AST_Defun)) {
compressor.warn("Dropping unreachable code [{file}:{line},{col}]", stat.start); compressor.warn("Dropping unreachable code [{file}:{line},{col}]", stat.start);
}
stat.walk(new TreeWalker(function(node){ stat.walk(new TreeWalker(function(node){
if (node instanceof AST_Var) { if (node instanceof AST_Var) {
compressor.warn("Declarations in unreachable code! [{file}:{line},{col}]", node.start); compressor.warn("Declarations in unreachable code! [{file}:{line},{col}]", node.start);
@@ -854,8 +1050,16 @@ merge(Compressor.prototype, {
: ev(this.alternative, compressor); : ev(this.alternative, compressor);
}); });
def(AST_SymbolRef, function(compressor){ def(AST_SymbolRef, function(compressor){
if (this._evaluating) throw def;
this._evaluating = true;
try {
var d = this.definition(); var d = this.definition();
if (d && d.constant && d.init) return ev(d.init, compressor); if (d && d.constant && d.init) {
return ev(d.init, compressor);
}
} finally {
this._evaluating = false;
}
throw def; throw def;
}); });
def(AST_Dot, function(compressor){ def(AST_Dot, function(compressor){
@@ -1092,13 +1296,14 @@ merge(Compressor.prototype, {
&& !self.uses_eval && !self.uses_eval
) { ) {
var in_use = []; var in_use = [];
var in_use_ids = {}; // avoid expensive linear scans of in_use
var initializations = new Dictionary(); var initializations = new Dictionary();
// pass 1: find out which symbols are directly used in // pass 1: find out which symbols are directly used in
// this scope (not in nested scopes). // this scope (not in nested scopes).
var scope = this; var scope = this;
var tw = new TreeWalker(function(node, descend){ var tw = new TreeWalker(function(node, descend){
if (node !== self) { if (node !== self) {
if (node instanceof AST_Defun) { if (node instanceof AST_Defun || node instanceof AST_DefClass) {
initializations.add(node.name.name, node); initializations.add(node.name.name, node);
return true; // don't go in nested scopes return true; // don't go in nested scopes
} }
@@ -1115,7 +1320,11 @@ merge(Compressor.prototype, {
return true; return true;
} }
if (node instanceof AST_SymbolRef) { if (node instanceof AST_SymbolRef) {
push_uniq(in_use, node.definition()); var node_def = node.definition();
if (!(node_def.id in in_use_ids)) {
in_use_ids[node_def.id] = true;
in_use.push(node_def);
}
return true; return true;
} }
if (node instanceof AST_Scope) { if (node instanceof AST_Scope) {
@@ -1138,7 +1347,11 @@ merge(Compressor.prototype, {
if (init) init.forEach(function(init){ if (init) init.forEach(function(init){
var tw = new TreeWalker(function(node){ var tw = new TreeWalker(function(node){
if (node instanceof AST_SymbolRef) { if (node instanceof AST_SymbolRef) {
push_uniq(in_use, node.definition()); var node_def = node.definition();
if (!(node_def.id in in_use_ids)) {
in_use_ids[node_def.id] = true;
in_use.push(node_def);
}
} }
}); });
init.walk(tw); init.walk(tw);
@@ -1178,9 +1391,7 @@ merge(Compressor.prototype, {
} }
} }
if ((node instanceof AST_Defun || node instanceof AST_DefClass) && node !== self) { if ((node instanceof AST_Defun || node instanceof AST_DefClass) && node !== self) {
var keep = var keep = (node.name.definition().id in in_use_ids) || node.name.definition().global;
member(node.name.definition(), in_use) ||
node.name.definition().global;
if (!keep) { if (!keep) {
compressor.warn("Dropping unused function {name} [{file}:{line},{col}]", { compressor.warn("Dropping unused function {name} [{file}:{line},{col}]", {
name : node.name.name, name : node.name.name,
@@ -1195,8 +1406,9 @@ merge(Compressor.prototype, {
if (node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn)) { if (node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn)) {
var def = node.definitions.filter(function(def){ var def = node.definitions.filter(function(def){
if (def.is_destructuring()) return true; if (def.is_destructuring()) return true;
if (member(def.name.definition(), in_use)) return true; if (def.name.definition().id in in_use_ids) return true;
if (def.name.definition().global) return true; if (def.name.definition().global) return true;
var w = { var w = {
name : def.name.name, name : def.name.name,
file : def.name.start.file, file : def.name.start.file,
@@ -1333,7 +1545,10 @@ merge(Compressor.prototype, {
var seq = node.to_assignments(); var seq = node.to_assignments();
var p = tt.parent(); var p = tt.parent();
if (p instanceof AST_ForIn && p.init === node) { if (p instanceof AST_ForIn && p.init === node) {
if (seq == null) return node.definitions[0].name; if (seq == null) {
var def = node.definitions[0].name;
return make_node(AST_SymbolRef, def, def);
}
return seq; return seq;
} }
if (p instanceof AST_For && p.init === node) { if (p instanceof AST_For && p.init === node) {
@@ -1564,9 +1779,13 @@ merge(Compressor.prototype, {
} }
if (is_empty(self.alternative)) self.alternative = null; if (is_empty(self.alternative)) self.alternative = null;
var negated = self.condition.negate(compressor); var negated = self.condition.negate(compressor);
var negated_is_best = best_of(self.condition, negated) === negated; var self_condition_length = self.condition.print_to_string().length;
var negated_length = negated.print_to_string().length;
var negated_is_best = negated_length < self_condition_length;
if (self.alternative && negated_is_best) { if (self.alternative && negated_is_best) {
negated_is_best = false; // because we already do the switch here. negated_is_best = false; // because we already do the switch here.
// no need to swap values of self_condition_length and negated_length
// here because they are only used in an equality comparison later on.
self.condition = negated; self.condition = negated;
var tmp = self.body; var tmp = self.body;
self.body = self.alternative || make_node(AST_EmptyStatement); self.body = self.alternative || make_node(AST_EmptyStatement);
@@ -1588,6 +1807,13 @@ merge(Compressor.prototype, {
}).transform(compressor); }).transform(compressor);
} }
if (is_empty(self.alternative) && self.body instanceof AST_SimpleStatement) { if (is_empty(self.alternative) && self.body instanceof AST_SimpleStatement) {
if (self_condition_length === negated_length && !negated_is_best
&& self.condition instanceof AST_Binary && self.condition.operator == "||") {
// although the code length of self.condition and negated are the same,
// negated does not require additional surrounding parentheses.
// see https://github.com/mishoo/UglifyJS2/issues/979
negated_is_best = true;
}
if (negated_is_best) return make_node(AST_SimpleStatement, self, { if (negated_is_best) return make_node(AST_SimpleStatement, self, {
body: make_node(AST_Binary, self, { body: make_node(AST_Binary, self, {
operator : "||", operator : "||",
@@ -2020,13 +2246,7 @@ merge(Compressor.prototype, {
if (!compressor.option("side_effects")) if (!compressor.option("side_effects"))
return self; return self;
if (!self.car.has_side_effects(compressor)) { if (!self.car.has_side_effects(compressor)) {
// we shouldn't compress (1,func)(something) to return maintain_this_binding(compressor.parent(), self, self.cdr);
// func(something) because that changes the meaning of
// the func (becomes lexical instead of global).
var p = compressor.parent();
if (!(p instanceof AST_Call && p.expression === self)) {
return self.cdr;
}
} }
if (compressor.option("cascade")) { if (compressor.option("cascade")) {
if (self.car instanceof AST_Assign if (self.car instanceof AST_Assign
@@ -2216,11 +2436,10 @@ merge(Compressor.prototype, {
if (ll.length > 1) { if (ll.length > 1) {
if (ll[1]) { if (ll[1]) {
compressor.warn("Condition left of && always true [{file}:{line},{col}]", self.start); compressor.warn("Condition left of && always true [{file}:{line},{col}]", self.start);
var rr = self.right.evaluate(compressor); return maintain_this_binding(compressor.parent(), self, self.right.evaluate(compressor)[0]);
return rr[0];
} else { } else {
compressor.warn("Condition left of && always false [{file}:{line},{col}]", self.start); compressor.warn("Condition left of && always false [{file}:{line},{col}]", self.start);
return ll[0]; return maintain_this_binding(compressor.parent(), self, ll[0]);
} }
} }
} }
@@ -2229,11 +2448,10 @@ merge(Compressor.prototype, {
if (ll.length > 1) { if (ll.length > 1) {
if (ll[1]) { if (ll[1]) {
compressor.warn("Condition left of || always true [{file}:{line},{col}]", self.start); compressor.warn("Condition left of || always true [{file}:{line},{col}]", self.start);
return ll[0]; return maintain_this_binding(compressor.parent(), self, ll[0]);
} else { } else {
compressor.warn("Condition left of || always false [{file}:{line},{col}]", self.start); compressor.warn("Condition left of || always false [{file}:{line},{col}]", self.start);
var rr = self.right.evaluate(compressor); return maintain_this_binding(compressor.parent(), self, self.right.evaluate(compressor)[0]);
return rr[0];
} }
} }
} }
@@ -2392,7 +2610,7 @@ merge(Compressor.prototype, {
if (self.undeclared() && !isLHS(self, compressor.parent())) { if (self.undeclared() && !isLHS(self, compressor.parent())) {
var defines = compressor.option("global_defs"); var defines = compressor.option("global_defs");
if (defines && defines.hasOwnProperty(self.name)) { if (defines && HOP(defines, self.name)) {
return make_node_from_constant(compressor, defines[self.name], self); return make_node_from_constant(compressor, defines[self.name], self);
} }
switch (self.name) { switch (self.name) {
@@ -2458,10 +2676,10 @@ merge(Compressor.prototype, {
if (cond.length > 1) { if (cond.length > 1) {
if (cond[1]) { if (cond[1]) {
compressor.warn("Condition always true [{file}:{line},{col}]", self.start); compressor.warn("Condition always true [{file}:{line},{col}]", self.start);
return self.consequent; return maintain_this_binding(compressor.parent(), self, self.consequent);
} else { } else {
compressor.warn("Condition always false [{file}:{line},{col}]", self.start); compressor.warn("Condition always false [{file}:{line},{col}]", self.start);
return self.alternative; return maintain_this_binding(compressor.parent(), self, self.alternative);
} }
} }
var negated = cond[0].negate(compressor); var negated = cond[0].negate(compressor);
@@ -2541,20 +2759,58 @@ merge(Compressor.prototype, {
} }
} }
// y?true:false --> !!y if (is_true(self.consequent)) {
if (is_true(consequent) && is_false(alternative)) { if (is_false(self.alternative)) {
self.condition = self.condition.negate(compressor); // c ? true : false ---> !!c
return make_node(AST_UnaryPrefix, self.condition, { return booleanize(self.condition);
operator: "!", }
expression: self.condition // c ? true : x ---> !!c || x
return make_node(AST_Binary, self, {
operator: "||",
left: booleanize(self.condition),
right: self.alternative
}); });
} }
// y?false:true --> !y if (is_false(self.consequent)) {
if (is_false(consequent) && is_true(alternative)) { if (is_true(self.alternative)) {
return self.condition.negate(compressor) // c ? false : true ---> !c
return booleanize(self.condition.negate(compressor));
} }
// c ? false : x ---> !c && x
return make_node(AST_Binary, self, {
operator: "&&",
left: booleanize(self.condition.negate(compressor)),
right: self.alternative
});
}
if (is_true(self.alternative)) {
// c ? x : true ---> !c || x
return make_node(AST_Binary, self, {
operator: "||",
left: booleanize(self.condition.negate(compressor)),
right: self.consequent
});
}
if (is_false(self.alternative)) {
// c ? x : false ---> !!c && x
return make_node(AST_Binary, self, {
operator: "&&",
left: booleanize(self.condition),
right: self.consequent
});
}
return self; return self;
function booleanize(node) {
if (node.is_boolean()) return node;
// !!expression
return make_node(AST_UnaryPrefix, node, {
operator: "!",
expression: node.negate(compressor)
});
}
// AST_True or !0 // AST_True or !0
function is_true(node) { function is_true(node) {
return node instanceof AST_True return node instanceof AST_True

View File

@@ -74,7 +74,7 @@ function OutputStream(options) {
var OUTPUT = ""; var OUTPUT = "";
function to_ascii(str, identifier) { function to_ascii(str, identifier) {
return str.replace(/[\u0080-\uffff]/g, function(ch) { return str.replace(/[\u0000-\u001f\u007f-\uffff]/g, function(ch) {
var code = ch.charCodeAt(0).toString(16); var code = ch.charCodeAt(0).toString(16);
if (code.length <= 2 && !identifier) { if (code.length <= 2 && !identifier) {
while (code.length < 2) code = "0" + code; while (code.length < 2) code = "0" + code;
@@ -90,16 +90,17 @@ function OutputStream(options) {
var dq = 0, sq = 0; var dq = 0, sq = 0;
str = str.replace(/[\\\b\f\n\r\v\t\x22\x27\u2028\u2029\0\ufeff]/g, function(s){ str = str.replace(/[\\\b\f\n\r\v\t\x22\x27\u2028\u2029\0\ufeff]/g, function(s){
switch (s) { switch (s) {
case '"': ++dq; return '"';
case "'": ++sq; return "'";
case "\\": return "\\\\"; case "\\": return "\\\\";
case "\b": return "\\b";
case "\f": return "\\f";
case "\n": return "\\n"; case "\n": return "\\n";
case "\r": return "\\r"; case "\r": return "\\r";
case "\t": return "\\t";
case "\b": return "\\b";
case "\f": return "\\f";
case "\x0B": return options.screw_ie8 ? "\\v" : "\\x0B"; case "\x0B": return options.screw_ie8 ? "\\v" : "\\x0B";
case "\u2028": return "\\u2028"; case "\u2028": return "\\u2028";
case "\u2029": return "\\u2029"; case "\u2029": return "\\u2029";
case '"': ++dq; return '"';
case "'": ++sq; return "'";
case "\0": return "\\x00"; case "\0": return "\\x00";
case "\ufeff": return "\\ufeff"; case "\ufeff": return "\\ufeff";
} }
@@ -449,11 +450,11 @@ function OutputStream(options) {
}); });
} else if (c.test) { } else if (c.test) {
comments = comments.filter(function(comment){ comments = comments.filter(function(comment){
return c.test(comment.value) || comment.type == "comment5"; return comment.type == "comment5" || c.test(comment.value);
}); });
} else if (typeof c == "function") { } else if (typeof c == "function") {
comments = comments.filter(function(comment){ comments = comments.filter(function(comment){
return c(self, comment) || comment.type == "comment5"; return comment.type == "comment5" || c(self, comment);
}); });
} }
@@ -601,8 +602,12 @@ function OutputStream(options) {
PARENS(AST_Number, function(output){ PARENS(AST_Number, function(output){
var p = output.parent(); var p = output.parent();
if (this.getValue() < 0 && p instanceof AST_PropAccess && p.expression === this) if (p instanceof AST_PropAccess && p.expression === this) {
var value = this.getValue();
if (value < 0 || /^0/.test(make_num(value))) {
return true; return true;
}
}
}); });
PARENS([ AST_Assign, AST_Conditional ], function (output){ PARENS([ AST_Assign, AST_Conditional ], function (output){
@@ -1180,7 +1185,7 @@ function OutputStream(options) {
var expr = self.expression; var expr = self.expression;
expr.print(output); expr.print(output);
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(".");
} }
} }

View File

@@ -46,7 +46,7 @@
var KEYWORDS = 'break case catch class const continue debugger default delete do else export extends finally for function if in instanceof new return switch throw try typeof var let void while with import yield'; var KEYWORDS = 'break case catch class const continue debugger default delete do else export extends finally for function if in instanceof new return switch throw try typeof var let void while with import yield';
var KEYWORDS_ATOM = 'false null true'; var KEYWORDS_ATOM = 'false null true';
var RESERVED_WORDS = 'abstract boolean byte char double enum export final float goto implements int interface long native package private protected public short static super synchronized this throws transient volatile yield' var RESERVED_WORDS = 'abstract boolean byte char class double enum export extends final float goto implements import int interface let long native package private protected public short static super synchronized this throws transient volatile'
+ " " + KEYWORDS_ATOM + " " + KEYWORDS; + " " + KEYWORDS_ATOM + " " + KEYWORDS;
var KEYWORDS_BEFORE_EXPRESSION = 'return new delete throw else case'; var KEYWORDS_BEFORE_EXPRESSION = 'return new delete throw else case';
@@ -409,7 +409,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
if (octal_len > 0) ch = String.fromCharCode(parseInt(ch, 8)); if (octal_len > 0) ch = String.fromCharCode(parseInt(ch, 8));
else ch = read_escaped_char(true); else ch = read_escaped_char(true);
} }
else if (ch == "\n") parse_error("Unterminated string constant"); else if ("\r\n\u2028\u2029".indexOf(ch) >= 0) parse_error("Unterminated string constant");
else if (ch == quote) break; else if (ch == quote) break;
ret += ch; ret += ch;
} }
@@ -431,7 +431,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
S.col = S.tokcol + (S.pos - S.tokpos); S.col = S.tokcol + (S.pos - S.tokpos);
S.comments_before.push(token(type, ret, true)); S.comments_before.push(token(type, ret, true));
S.regex_allowed = regex_allowed; S.regex_allowed = regex_allowed;
return next_token(); return next_token;
}; };
var skip_multiline_comment = with_eof_error("Unterminated multiline comment", function(){ var skip_multiline_comment = with_eof_error("Unterminated multiline comment", function(){
@@ -449,7 +449,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
S.comments_before.push(token("comment2", text, true)); S.comments_before.push(token("comment2", text, true));
S.regex_allowed = regex_allowed; S.regex_allowed = regex_allowed;
S.newline_before = nlb; S.newline_before = nlb;
return next_token(); return next_token;
}); });
function read_name() { function read_name() {
@@ -575,16 +575,19 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
function next_token(force_regexp) { function next_token(force_regexp) {
if (force_regexp != null) if (force_regexp != null)
return read_regexp(force_regexp); return read_regexp(force_regexp);
for (;;) {
skip_whitespace(); skip_whitespace();
start_token(); start_token();
if (html5_comments) { if (html5_comments) {
if (looking_at("<!--")) { if (looking_at("<!--")) {
forward(4); forward(4);
return skip_line_comment("comment3"); skip_line_comment("comment3");
continue;
} }
if (looking_at("-->") && S.newline_before) { if (looking_at("-->") && S.newline_before) {
forward(3); forward(3);
return skip_line_comment("comment4"); skip_line_comment("comment4");
continue;
} }
} }
var ch = peek(); var ch = peek();
@@ -593,20 +596,26 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
switch (code) { switch (code) {
case 34: case 39: return read_string(ch); case 34: case 39: return read_string(ch);
case 46: return handle_dot(); case 46: return handle_dot();
case 47: return handle_slash(); case 47: {
var tok = handle_slash();
if (tok === next_token) continue;
return tok;
}
case 61: return handle_eq_sign(); case 61: return handle_eq_sign();
} }
if (is_digit(code)) return read_num(); if (is_digit(code)) return read_num();
if (PUNC_CHARS(ch)) return token("punc", next()); if (PUNC_CHARS(ch)) return token("punc", next());
if (OPERATOR_CHARS(ch)) return read_operator(); if (OPERATOR_CHARS(ch)) return read_operator();
if (code == 92 || is_identifier_start(code)) return read_word(); if (code == 92 || is_identifier_start(code)) return read_word();
if (shebang) { if (shebang) {
if (S.pos == 0 && looking_at("#!")) { if (S.pos == 0 && looking_at("#!")) {
forward(2); forward(2);
return skip_line_comment("comment5"); skip_line_comment("comment5");
continue;
} }
} }
break;
}
parse_error("Unexpected character '" + ch + "'"); parse_error("Unexpected character '" + ch + "'");
}; };
@@ -1379,6 +1388,13 @@ function parse($TEXT, options) {
break; break;
} }
break; break;
case "operator":
if (!is_identifier_string(tok.value)) {
throw new JS_Parse_Error("Invalid getter/setter name: " + tok.value,
tok.file, tok.line, tok.col, tok.pos);
}
ret = _make_symbol(AST_SymbolRef);
break;
} }
next(); next();
return ret; return ret;

View File

@@ -55,8 +55,11 @@ function SymbolDef(scope, index, orig) {
this.undeclared = false; this.undeclared = false;
this.constant = false; this.constant = false;
this.index = index; this.index = index;
this.id = SymbolDef.next_id++;
}; };
SymbolDef.next_id = 1;
SymbolDef.prototype = { SymbolDef.prototype = {
unmangleable: function(options) { unmangleable: function(options) {
if (!options) options = {}; if (!options) options = {};
@@ -102,6 +105,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
var scope = self.parent_scope = null; var scope = self.parent_scope = null;
var labels = new Dictionary(); var labels = new Dictionary();
var defun = null; var defun = null;
var last_var_had_const_pragma = false;
var nesting = 0; var nesting = 0;
var in_destructuring = null; var in_destructuring = null;
var in_export; var in_export;
@@ -195,11 +199,14 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
// inside the class. // inside the class.
(node.scope = defun.parent_scope).def_function(node, in_export); (node.scope = defun.parent_scope).def_function(node, in_export);
} }
else if (node instanceof AST_Var) {
last_var_had_const_pragma = node.has_const_pragma();
}
else if (node instanceof AST_SymbolVar else if (node instanceof AST_SymbolVar
|| node instanceof AST_SymbolConst || node instanceof AST_SymbolConst
|| node instanceof AST_SymbolLet) { || node instanceof AST_SymbolLet) {
var def = ((node instanceof AST_SymbolBlockDeclaration) ? scope : defun).def_variable(node, in_export); var def = ((node instanceof AST_SymbolBlockDeclaration) ? scope : defun).def_variable(node, in_export);
def.constant = node instanceof AST_SymbolConst; def.constant = node instanceof AST_SymbolConst || last_var_had_const_pragma;
def.destructuring = in_destructuring; def.destructuring = in_destructuring;
def.init = tw.parent().value; def.init = tw.parent().value;
} }
@@ -244,6 +251,11 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
} }
if (node instanceof AST_SymbolRef) { if (node instanceof AST_SymbolRef) {
var name = node.name; var name = node.name;
if (name == "eval" && tw.parent() instanceof AST_Call) {
for (var s = node.scope; s && !s.uses_eval; s = s.parent_scope) {
s.uses_eval = true;
}
}
var sym = node.scope.find_variable(name); var sym = node.scope.find_variable(name);
if (!sym) { if (!sym) {
var g; var g;
@@ -256,10 +268,6 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
globals.set(name, g); globals.set(name, g);
} }
node.thedef = g; node.thedef = g;
if (name == "eval" && tw.parent() instanceof AST_Call) {
for (var s = node.scope; s && !s.uses_eval; s = s.parent_scope)
s.uses_eval = true;
}
if (func && name == "arguments") { if (func && name == "arguments") {
func.uses_arguments = true; func.uses_arguments = true;
} }
@@ -299,6 +307,10 @@ AST_Block.DEFMETHOD("creates_block_scope", function() {
AST_Lambda.DEFMETHOD("init_scope_vars", function(){ AST_Lambda.DEFMETHOD("init_scope_vars", function(){
AST_Scope.prototype.init_scope_vars.apply(this, arguments); AST_Scope.prototype.init_scope_vars.apply(this, arguments);
this.uses_arguments = false; this.uses_arguments = false;
var symbol = new AST_VarDef({ name: "arguments", start: this.start, end: this.end });
var def = new SymbolDef(this, this.variables.size(), symbol);
this.variables.set(symbol.name, def);
}); });
AST_SymbolRef.DEFMETHOD("reference", function() { AST_SymbolRef.DEFMETHOD("reference", function() {
@@ -420,11 +432,17 @@ AST_Symbol.DEFMETHOD("global", function(){
return this.definition().global; return this.definition().global;
}); });
AST_Var.DEFMETHOD("has_const_pragma", function() {
var comments_before = this.start && this.start.comments_before;
var lastComment = comments_before && comments_before[comments_before.length - 1];
return lastComment && /@const\b/.test(lastComment.value);
});
AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){ AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){
return defaults(options, { return defaults(options, {
except : [], except : [],
eval : false, eval : false,
sort : false, sort : false, // Ignored. Flag retained for backwards compatibility.
toplevel : false, toplevel : false,
screw_ie8 : false, screw_ie8 : false,
keep_fnames : false, keep_fnames : false,
@@ -434,6 +452,10 @@ AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){
AST_Toplevel.DEFMETHOD("mangle_names", function(options){ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
options = this._default_mangler_options(options); options = this._default_mangler_options(options);
// Never mangle arguments
options.except.push('arguments');
// We only need to mangle declaration nodes. Special logic wired // We only need to mangle declaration nodes. Special logic wired
// into the code generator will display the mangled name if it's // into the code generator will display the mangled name if it's
// present (and for AST_SymbolRef-s it'll use the mangled name of // present (and for AST_SymbolRef-s it'll use the mangled name of
@@ -464,9 +486,6 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
a.push(symbol); a.push(symbol);
} }
}); });
if (options.sort) a.sort(function(a, b){
return b.references.length - a.references.length;
});
to_mangle.push.apply(to_mangle, a); to_mangle.push.apply(to_mangle, a);
return; return;
} }

View File

@@ -53,16 +53,11 @@ function SourceMap(options) {
orig_line_diff : 0, orig_line_diff : 0,
dest_line_diff : 0, dest_line_diff : 0,
}); });
var orig_map = options.orig && new MOZ_SourceMap.SourceMapConsumer(options.orig); var generator = new MOZ_SourceMap.SourceMapGenerator({
var generator;
if (orig_map) {
generator = MOZ_SourceMap.SourceMapGenerator.fromSourceMap(orig_map);
} else {
generator = new MOZ_SourceMap.SourceMapGenerator({
file : options.file, file : options.file,
sourceRoot : options.root sourceRoot : options.root
}); });
} var orig_map = options.orig && new MOZ_SourceMap.SourceMapConsumer(options.orig);
function add(source, gen_line, gen_col, orig_line, orig_col, name) { function add(source, gen_line, gen_col, orig_line, orig_col, name) {
if (orig_map) { if (orig_map) {
var info = orig_map.originalPositionFor({ var info = orig_map.originalPositionFor({
@@ -83,7 +78,7 @@ function SourceMap(options) {
source : source, source : source,
name : name name : name
}); });
} };
return { return {
add : add, add : add,
get : function() { return generator }, get : function() { return generator },

View File

@@ -64,7 +64,7 @@ TreeTransformer.prototype = new TreeWalker;
x = this; x = this;
descend(x, tw); descend(x, tw);
} else { } else {
tw.stack[tw.stack.length - 1] = x = this.clone(); tw.stack[tw.stack.length - 1] = x = this;
descend(x, tw); descend(x, tw);
y = tw.after(x, in_list); y = tw.after(x, in_list);
if (y !== undefined) x = y; if (y !== undefined) x = y;

View File

@@ -59,10 +59,7 @@ function characters(str) {
}; };
function member(name, array) { function member(name, array) {
for (var i = array.length; --i >= 0;) return array.indexOf(name) >= 0;
if (array[i] == name)
return true;
return false;
}; };
function find_if(func, array) { function find_if(func, array) {
@@ -97,17 +94,17 @@ function defaults(args, defs, croak) {
if (args === true) if (args === true)
args = {}; args = {};
var ret = args || {}; var ret = args || {};
if (croak) for (var i in ret) if (ret.hasOwnProperty(i) && !defs.hasOwnProperty(i)) if (croak) for (var i in ret) if (HOP(ret, i) && !HOP(defs, i))
DefaultsError.croak("`" + i + "` is not a supported option", defs); DefaultsError.croak("`" + i + "` is not a supported option", defs);
for (var i in defs) if (defs.hasOwnProperty(i)) { for (var i in defs) if (HOP(defs, i)) {
ret[i] = (args && args.hasOwnProperty(i)) ? args[i] : defs[i]; ret[i] = (args && HOP(args, i)) ? args[i] : defs[i];
} }
return ret; return ret;
}; };
function merge(obj, ext) { function merge(obj, ext) {
var count = 0; var count = 0;
for (var i in ext) if (ext.hasOwnProperty(i)) { for (var i in ext) if (HOP(ext, i)) {
obj[i] = ext[i]; obj[i] = ext[i];
count++; count++;
} }
@@ -150,7 +147,7 @@ var MAP = (function(){
} }
} }
else { else {
for (i in a) if (a.hasOwnProperty(i)) if (doit()) break; for (i in a) if (HOP(a, i)) if (doit()) break;
} }
return top.concat(ret); return top.concat(ret);
}; };
@@ -308,3 +305,7 @@ Dictionary.fromObject = function(obj) {
dict._size = merge(dict._values, obj); dict._size = merge(dict._values, obj);
return dict; return dict;
}; };
function HOP(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}

View File

@@ -4,7 +4,7 @@
"homepage": "http://lisperator.net/uglifyjs", "homepage": "http://lisperator.net/uglifyjs",
"author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)", "author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"version": "2.6.1", "version": "2.6.2",
"engines": { "engines": {
"node": ">=0.8.0" "node": ">=0.8.0"
}, },
@@ -38,7 +38,8 @@
"acorn": "~0.6.0", "acorn": "~0.6.0",
"escodegen": "~1.3.3", "escodegen": "~1.3.3",
"esfuzz": "~0.3.1", "esfuzz": "~0.3.1",
"estraverse": "~1.5.1" "estraverse": "~1.5.1",
"mocha": "~2.3.4"
}, },
"browserify": { "browserify": {
"transform": [ "transform": [
@@ -48,5 +49,6 @@
"scripts": { "scripts": {
"shrinkwrap": "rm ./npm-shrinkwrap.json; rm -rf ./node_modules; npm i && npm shrinkwrap && npm outdated", "shrinkwrap": "rm ./npm-shrinkwrap.json; rm -rf ./node_modules; npm i && npm shrinkwrap && npm outdated",
"test": "node test/run-tests.js" "test": "node test/run-tests.js"
} },
"keywords": ["uglify", "uglify-js", "minify", "minifier"]
} }

32
test/compress/ascii.js Normal file
View File

@@ -0,0 +1,32 @@
ascii_only_true: {
options = {}
beautify = {
ascii_only : true,
beautify : false,
}
input: {
function f() {
return "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" +
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
"\x20\x21\x22\x23 ... \x7d\x7e\x7f\x80\x81 ... \xfe\xff\u0fff\uffff";
}
}
expect_exact: 'function f(){return"\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\b\\t\\n\\x0B\\f\\r\\x0e\\x0f"+"\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f"+\' !"# ... }~\\x7f\\x80\\x81 ... \\xfe\\xff\\u0fff\\uffff\'}'
}
ascii_only_false: {
options = {}
beautify = {
ascii_only : false,
beautify : false,
}
input: {
function f() {
return "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" +
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
"\x20\x21\x22\x23 ... \x7d\x7e\x7f\x80\x81 ... \xfe\xff\u0fff\uffff";
}
}
expect_exact: 'function f(){return"\\x00\x01\x02\x03\x04\x05\x06\x07\\b\\t\\n\\x0B\\f\\r\x0e\x0f"+"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"+\' !"# ... }~\x7f\x80\x81 ... \xfe\xff\u0fff\uffff\'}'
}

View File

@@ -130,4 +130,3 @@ regression_block_scope_resolves: {
}()); }());
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -407,8 +407,8 @@ cond_8: {
a = !condition; a = !condition;
a = !condition; a = !condition;
a = condition ? 1 : false; a = !!condition && 1;
a = condition ? 0 : true; a = !condition || 0;
a = condition ? 1 : 0; a = condition ? 1 : 0;
} }
} }
@@ -490,8 +490,8 @@ cond_8b: {
a = !condition; a = !condition;
a = !condition; a = !condition;
a = condition ? 1 : !1; a = !!condition && 1;
a = condition ? 0 : !0; a = !condition || 0;
a = condition ? 1 : 0; a = condition ? 1 : 0;
} }
} }
@@ -557,7 +557,7 @@ cond_8c: {
a = !!condition; a = !!condition;
a = !condition; a = !condition;
a = condition() ? !0 : !-3.5; a = !!condition() || !-3.5;
a = !!condition; a = !!condition;
a = !!condition; a = !!condition;
@@ -573,12 +573,68 @@ cond_8c: {
a = !condition; a = !condition;
a = !condition; a = !condition;
a = condition ? 1 : false; a = !!condition && 1;
a = condition ? 0 : true; a = !condition || 0;
a = condition ? 1 : 0; a = condition ? 1 : 0;
} }
} }
ternary_boolean_consequent: {
options = {
collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true,
comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true,
keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true
}
input: {
function f1() { return a == b ? true : x; }
function f2() { return a == b ? false : x; }
function f3() { return a < b ? !0 : x; }
function f4() { return a < b ? !1 : x; }
function f5() { return c ? !0 : x; }
function f6() { return c ? false : x; }
function f7() { return !c ? true : x; }
function f8() { return !c ? !1 : x; }
}
expect: {
function f1() { return a == b || x; }
function f2() { return a != b && x; }
function f3() { return a < b || x; }
function f4() { return !(a < b) && x; }
function f5() { return !!c || x; }
function f6() { return !c && x; }
function f7() { return !c || x; }
function f8() { return !!c && x; }
}
}
ternary_boolean_alternative: {
options = {
collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true,
comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true,
keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true
}
input: {
function f1() { return a == b ? x : true; }
function f2() { return a == b ? x : false; }
function f3() { return a < b ? x : !0; }
function f4() { return a < b ? x : !1; }
function f5() { return c ? x : true; }
function f6() { return c ? x : !1; }
function f7() { return !c ? x : !0; }
function f8() { return !c ? x : false; }
}
expect: {
function f1() { return a != b || x; }
function f2() { return a == b && x; }
function f3() { return !(a < b) || x; }
function f4() { return a < b && x; }
function f5() { return !c || x; }
function f6() { return !!c && x; }
function f7() { return !!c || x; }
function f8() { return !c && x; }
}
}
conditional_and: { conditional_and: {
options = { options = {
conditionals: true, conditionals: true,
@@ -738,3 +794,77 @@ conditional_or: {
a = condition + 3 || null; a = condition + 3 || null;
} }
} }
trivial_boolean_ternary_expressions : {
options = {
conditionals: true,
evaluate : true,
booleans : true
};
input: {
f('foo' in m ? true : false);
f('foo' in m ? false : true);
f(g ? true : false);
f(foo() ? true : false);
f("bar" ? true : false);
f(5 ? true : false);
f(5.7 ? true : false);
f(x - y ? true : false);
f(x == y ? true : false);
f(x === y ? !0 : !1);
f(x < y ? !0 : false);
f(x <= y ? true : false);
f(x > y ? true : !1);
f(x >= y ? !0 : !1);
f(g ? false : true);
f(foo() ? false : true);
f("bar" ? false : true);
f(5 ? false : true);
f(5.7 ? false : true);
f(x - y ? false : true);
f(x == y ? !1 : !0);
f(x === y ? false : true);
f(x < y ? false : true);
f(x <= y ? false : !0);
f(x > y ? !1 : true);
f(x >= y ? !1 : !0);
}
expect: {
f('foo' in m);
f(!('foo' in m));
f(!!g);
f(!!foo());
f(!0);
f(!0);
f(!0);
f(!!(x - y));
f(x == y);
f(x === y);
f(x < y);
f(x <= y);
f(x > y);
f(x >= y);
f(!g);
f(!foo());
f(!1);
f(!1);
f(!1);
f(!(x - y));
f(x != y);
f(x !== y);
f(!(x < y));
f(!(x <= y));
f(!(x > y));
f(!(x >= y));
}
}

View File

@@ -109,3 +109,120 @@ dead_code_block_decls_die: {
console.log(foo, bar, Baz); console.log(foo, bar, Baz);
} }
} }
dead_code_const_declaration: {
options = {
dead_code : true,
loops : true,
booleans : true,
conditionals : true,
evaluate : true
};
input: {
var unused;
const CONST_FOO = false;
if (CONST_FOO) {
console.log("unreachable");
var moo;
function bar() {}
}
}
expect: {
var unused;
const CONST_FOO = !1;
var moo;
function bar() {}
}
}
dead_code_const_annotation: {
options = {
dead_code : true,
loops : true,
booleans : true,
conditionals : true,
evaluate : true
};
input: {
var unused;
/** @const */ var CONST_FOO_ANN = false;
if (CONST_FOO_ANN) {
console.log("unreachable");
var moo;
function bar() {}
}
}
expect: {
var unused;
var CONST_FOO_ANN = !1;
var moo;
function bar() {}
}
}
dead_code_const_annotation_regex: {
options = {
dead_code : true,
loops : true,
booleans : true,
conditionals : true,
evaluate : true
};
input: {
var unused;
// @constraint this shouldn't be a constant
var CONST_FOO_ANN = false;
if (CONST_FOO_ANN) {
console.log("reachable");
}
}
expect: {
var unused;
var CONST_FOO_ANN = !1;
CONST_FOO_ANN && console.log('reachable');
}
}
dead_code_const_annotation_complex_scope: {
options = {
dead_code : true,
loops : true,
booleans : true,
conditionals : true,
evaluate : true
};
input: {
var unused_var;
/** @const */ var test = 'test';
// @const
var CONST_FOO_ANN = false;
var unused_var_2;
if (CONST_FOO_ANN) {
console.log("unreachable");
var moo;
function bar() {}
}
if (test === 'test') {
var beef = 'good';
/** @const */ var meat = 'beef';
var pork = 'bad';
if (meat === 'pork') {
console.log('also unreachable');
} else if (pork === 'good') {
console.log('reached, not const');
}
}
}
expect: {
var unused_var;
var test = 'test';
var CONST_FOO_ANN = !1;
var unused_var_2;
var moo;
function bar() {}
var beef = 'good';
var meat = 'beef';
var pork = 'bad';
'good' === pork && console.log('reached, not const');
}
}

137
test/compress/issue-1034.js Normal file
View File

@@ -0,0 +1,137 @@
non_hoisted_function_after_return: {
options = {
hoist_funs: false, dead_code: true, conditionals: true, comparisons: true,
evaluate: true, booleans: true, loops: true, unused: true, keep_fargs: true,
if_return: true, join_vars: true, cascade: true, side_effects: true
}
input: {
function foo(x) {
if (x) {
return bar();
not_called1();
} else {
return baz();
not_called2();
}
function bar() { return 7; }
return not_reached;
function UnusedFunction() {}
function baz() { return 8; }
}
}
expect: {
function foo(x) {
return x ? bar() : baz();
function bar() { return 7 }
function baz() { return 8 }
}
}
expect_warnings: [
'WARN: Dropping unreachable code [test/compress/issue-1034.js:11,16]',
"WARN: Dropping unreachable code [test/compress/issue-1034.js:14,16]",
"WARN: Dropping unreachable code [test/compress/issue-1034.js:17,12]",
"WARN: Dropping unused function UnusedFunction [test/compress/issue-1034.js:18,21]"
]
}
non_hoisted_function_after_return_2a: {
options = {
hoist_funs: false, dead_code: true, conditionals: true, comparisons: true,
evaluate: true, booleans: true, loops: true, unused: true, keep_fargs: true,
if_return: true, join_vars: true, cascade: true, side_effects: true,
collapse_vars: false
}
input: {
function foo(x) {
if (x) {
return bar(1);
var a = not_called(1);
} else {
return bar(2);
var b = not_called(2);
}
var c = bar(3);
function bar(x) { return 7 - x; }
function nope() {}
return b || c;
}
}
expect: {
// NOTE: Output is correct, but suboptimal. Not a regression. Can be improved in future.
// This output is run through non_hoisted_function_after_return_2b with same flags.
function foo(x) {
if (x) {
return bar(1);
} else {
return bar(2);
var b;
}
var c = bar(3);
function bar(x) {
return 7 - x;
}
return b || c;
}
}
expect_warnings: [
"WARN: Dropping unreachable code [test/compress/issue-1034.js:48,16]",
"WARN: Declarations in unreachable code! [test/compress/issue-1034.js:48,16]",
"WARN: Dropping 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 function nope [test/compress/issue-1034.js:55,21]"
]
}
non_hoisted_function_after_return_2b: {
options = {
hoist_funs: false, dead_code: true, conditionals: true, comparisons: true,
evaluate: true, booleans: true, loops: true, unused: true, keep_fargs: true,
if_return: true, join_vars: true, cascade: true, side_effects: true,
collapse_vars: false
}
input: {
// Note: output of test non_hoisted_function_after_return_2a run through compress again
function foo(x) {
if (x) {
return bar(1);
} else {
return bar(2);
var b;
}
var c = bar(3);
function bar(x) {
return 7 - x;
}
return b || c;
}
}
expect: {
// the output we would have liked to see from non_hoisted_function_after_return_2a
function foo(x) {
return bar(x ? 1 : 2);
function bar(x) { return 7 - x; }
}
}
expect_warnings: [
// Notice that some warnings are repeated by multiple compress passes.
// Not a regression. There is room for improvement here.
// Warnings should be cached and only output if unique.
"WARN: Dropping unreachable code [test/compress/issue-1034.js:100,16]",
"WARN: Declarations in unreachable code! [test/compress/issue-1034.js:100,16]",
"WARN: Dropping unreachable code [test/compress/issue-1034.js:100,16]",
"WARN: Declarations in unreachable code! [test/compress/issue-1034.js:100,16]",
"WARN: Dropping unreachable code [test/compress/issue-1034.js:100,16]",
"WARN: Declarations in unreachable code! [test/compress/issue-1034.js:100,16]",
"WARN: Dropping unreachable code [test/compress/issue-1034.js:100,16]",
"WARN: Declarations in unreachable code! [test/compress/issue-1034.js:100,16]",
"WARN: Dropping unreachable code [test/compress/issue-1034.js:102,12]",
"WARN: Declarations in unreachable code! [test/compress/issue-1034.js:102,12]",
"WARN: Dropping unreachable code [test/compress/issue-1034.js:106,12]",
"WARN: Dropping unreachable code [test/compress/issue-1034.js:100,16]",
"WARN: Declarations in unreachable code! [test/compress/issue-1034.js:100,16]",
"WARN: Dropping unused variable b [test/compress/issue-1034.js:100,20]",
"WARN: Dropping unused variable c [test/compress/issue-1034.js:102,16]"
]
}

View File

@@ -0,0 +1,39 @@
const_declaration: {
options = {
evaluate: true
};
input: {
const goog = goog || {};
}
expect: {
const goog = goog || {};
}
}
const_pragma: {
options = {
evaluate: true
};
input: {
/** @const */ var goog = goog || {};
}
expect: {
var goog = goog || {};
}
}
// for completeness' sake
not_const: {
options = {
evaluate: true
};
input: {
var goog = goog || {};
}
expect: {
var goog = goog || {};
}
}

View File

@@ -9,3 +9,50 @@ keep_name_of_setter: {
input: { a = { set foo () {} } } input: { a = { set foo () {} } }
expect: { a = { set foo () {} } } expect: { a = { set foo () {} } }
} }
setter_with_operator_keys: {
input: {
var tokenCodes = {
get instanceof(){
return test0;
},
set instanceof(value){
test0 = value;
},
set typeof(value){
test1 = value;
},
get typeof(){
return test1;
},
set else(value){
test2 = value;
},
get else(){
return test2;
}
};
}
expect: {
var tokenCodes = {
get instanceof(){
return test0;
},
set instanceof(value){
test0 = value;
},
set typeof(value){
test1 = value;
},
get typeof(){
return test1;
},
set else(value){
test2 = value;
},
get else(){
return test2;
}
};
}
}

View File

@@ -1,23 +1,27 @@
remove_redundant_sequence_items: { remove_redundant_sequence_items: {
options = { side_effects: true }; options = { side_effects: true };
input: { input: {
(0, 1, eval)();
(0, 1, logThis)(); (0, 1, logThis)();
(0, 1, _decorators.logThis)(); (0, 1, _decorators.logThis)();
} }
expect: { expect: {
(0, logThis)(); (0, eval)();
logThis();
(0, _decorators.logThis)(); (0, _decorators.logThis)();
} }
} }
dont_remove_lexical_binding_sequence: { dont_remove_this_binding_sequence: {
options = { side_effects: true }; options = { side_effects: true };
input: { input: {
(0, eval)();
(0, logThis)(); (0, logThis)();
(0, _decorators.logThis)(); (0, _decorators.logThis)();
} }
expect: { expect: {
(0, logThis)(); (0, eval)();
logThis();
(0, _decorators.logThis)(); (0, _decorators.logThis)();
} }
} }

View File

@@ -0,0 +1,32 @@
dont_mangle_arguments: {
mangle = {
};
options = {
sequences : true,
properties : true,
dead_code : true,
drop_debugger : true,
conditionals : true,
comparisons : true,
evaluate : true,
booleans : true,
loops : true,
unused : true,
hoist_funs : true,
keep_fargs : true,
keep_fnames : false,
hoist_vars : true,
if_return : true,
join_vars : true,
cascade : true,
side_effects : true,
negate_iife : false
};
input: {
(function(){
var arguments = arguments, not_arguments = 9;
console.log(not_arguments, arguments);
})(5,6,7);
}
expect_exact: "(function(){var arguments=arguments,o=9;console.log(o,arguments)})(5,6,7);"
}

View File

@@ -0,0 +1,20 @@
keep_var_for_in: {
options = {
hoist_vars: true,
unused: true
};
input: {
(function(obj){
var foo = 5;
for (var i in obj)
return foo;
})();
}
expect: {
(function(obj){
var i, foo = 5;
for (i in obj)
return foo;
})();
}
}

View File

@@ -0,0 +1,96 @@
this_binding_conditionals: {
options = {
conditionals: true,
evaluate : true
};
input: {
(1 && a)();
(0 || a)();
(0 || 1 && a)();
(1 ? a : 0)();
(1 && a.b)();
(0 || a.b)();
(0 || 1 && a.b)();
(1 ? a.b : 0)();
(1 && a[b])();
(0 || a[b])();
(0 || 1 && a[b])();
(1 ? a[b] : 0)();
(1 && eval)();
(0 || eval)();
(0 || 1 && eval)();
(1 ? eval : 0)();
}
expect: {
a();
a();
a();
a();
(0, a.b)();
(0, a.b)();
(0, a.b)();
(0, a.b)();
(0, a[b])();
(0, a[b])();
(0, a[b])();
(0, a[b])();
(0, eval)();
(0, eval)();
(0, eval)();
(0, eval)();
}
}
this_binding_collapse_vars: {
options = {
collapse_vars: true,
};
input: {
var c = a; c();
var d = a.b; d();
var e = eval; e();
}
expect: {
a();
(0, a.b)();
(0, eval)();
}
}
this_binding_side_effects: {
options = {
side_effects : true
};
input: {
(function (foo) {
(0, foo)();
(0, foo.bar)();
(0, eval)('console.log(foo);');
}());
(function (foo) {
var eval = console;
(0, foo)();
(0, foo.bar)();
(0, eval)('console.log(foo);');
}());
}
expect: {
(function (foo) {
foo();
(0, foo.bar)();
(0, eval)('console.log(foo);');
}());
(function (foo) {
var eval = console;
foo();
(0, foo.bar)();
(0, eval)('console.log(foo);');
}());
}
}

View File

@@ -0,0 +1,88 @@
eval_collapse_vars: {
options = {
collapse_vars:true, sequences:false, properties:true, dead_code:true, conditionals:true,
comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true,
keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true
};
input: {
function f1() {
var e = 7;
var s = "abcdef";
var i = 2;
var eval = console.log.bind(console);
var x = s.charAt(i++);
var y = s.charAt(i++);
var z = s.charAt(i++);
eval(x, y, z, e);
}
function p1() { var a = foo(), b = bar(), eval = baz(); return a + b + eval; }
function p2() { var a = foo(), b = bar(), eval = baz; return a + b + eval(); }
(function f2(eval) {
var a = 2;
console.log(a - 5);
eval("console.log(a);");
})(eval);
}
expect: {
function f1() {
var e = 7,
s = "abcdef",
i = 2,
eval = console.log.bind(console),
x = s.charAt(i++),
y = s.charAt(i++),
z = s.charAt(i++);
eval(x, y, z, e);
}
function p1() { return foo() + bar() + baz(); }
function p2() { var a = foo(), b = bar(), eval = baz; return a + b + eval(); }
(function f2(eval) {
var a = 2;
console.log(a - 5);
eval("console.log(a);");
})(eval);
}
}
eval_unused: {
options = { unused: true, keep_fargs: false };
input: {
function f1(a, eval, c, d, e) {
return a('c') + eval;
}
function f2(a, b, c, d, e) {
return a + eval('c');
}
function f3(a, eval, c, d, e) {
return a + eval('c');
}
}
expect: {
function f1(a, eval) {
return a('c') + eval;
}
function f2(a, b, c, d, e) {
return a + eval('c');
}
function f3(a, eval, c, d, e) {
return a + eval('c');
}
}
}
eval_mangle: {
mangle = {
};
input: {
function f1(a, eval, c, d, e) {
return a('c') + eval;
}
function f2(a, b, c, d, e) {
return a + eval('c');
}
function f3(a, eval, c, d, e) {
return a + eval('c');
}
}
expect_exact: 'function f1(n,c,e,a,f){return n("c")+c}function f2(a,b,c,d,e){return a+eval("c")}function f3(a,eval,c,d,e){return a+eval("c")}'
}

View File

@@ -0,0 +1,89 @@
issue979_reported: {
options = {
sequences:true, properties:true, dead_code:true, conditionals:true,
comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true,
keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true
}
input: {
function f1() {
if (a == 1 || b == 2) {
foo();
}
}
function f2() {
if (!(a == 1 || b == 2)) {
}
else {
foo();
}
}
}
expect: {
function f1() {
1!=a&&2!=b||foo();
}
function f2() {
1!=a&&2!=b||foo();
}
}
}
issue979_test_negated_is_best: {
options = {
sequences:true, properties:true, dead_code:true, conditionals:true,
comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true,
keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true
}
input: {
function f3() {
if (a == 1 | b == 2) {
foo();
}
}
function f4() {
if (!(a == 1 | b == 2)) {
}
else {
foo();
}
}
function f5() {
if (a == 1 && b == 2) {
foo();
}
}
function f6() {
if (!(a == 1 && b == 2)) {
}
else {
foo();
}
}
function f7() {
if (a == 1 || b == 2) {
foo();
}
else {
return bar();
}
}
}
expect: {
function f3() {
1==a|2==b&&foo();
}
function f4() {
1==a|2==b&&foo();
}
function f5() {
1==a&&2==b&&foo();
}
function f6() {
1!=a||2!=b||foo();
}
function f7() {
return 1!=a&&2!=b?bar():void foo();
}
}
}

19
test/compress/numbers.js Normal file
View File

@@ -0,0 +1,19 @@
hex_numbers_in_parentheses_for_prototype_functions: {
input: {
(-2);
(-2).toFixed(0);
(2);
(2).toFixed(0);
(0.2);
(0.2).toFixed(0);
(0.00000002);
(0.00000002).toFixed(0);
(1000000000000000128);
(1000000000000000128).toFixed(0);
}
expect_exact: "-2;(-2).toFixed(0);2;2..toFixed(0);.2;.2.toFixed(0);2e-8;2e-8.toFixed(0);0xde0b6b3a7640080;(0xde0b6b3a7640080).toFixed(0);"
}

29
test/mocha.js Normal file
View File

@@ -0,0 +1,29 @@
var Mocha = require('mocha'),
fs = require('fs'),
path = require('path');
// Instantiate a Mocha instance.
var mocha = new Mocha({});
var testDir = __dirname + '/mocha/';
// Add each .js file to the mocha instance
fs.readdirSync(testDir).filter(function(file){
// Only keep the .js files
return file.substr(-3) === '.js';
}).forEach(function(file){
mocha.addFile(
path.join(testDir, file)
);
});
module.exports = function() {
mocha.run(function(failures) {
if (failures !== 0) {
process.on('exit', function () {
process.exit(failures);
});
}
});
};

22
test/mocha/arguments.js Normal file
View File

@@ -0,0 +1,22 @@
var UglifyJS = require('../../');
var assert = require("assert");
describe("arguments", function() {
it("Should known that arguments in functions are local scoped", function() {
var ast = UglifyJS.parse("var arguments; var f = function() {arguments.length}");
ast.figure_out_scope();
// Test scope of `var arguments`
assert.strictEqual(ast.find_variable("arguments").global, true);
// Select arguments symbol in function
var symbol = ast.body[1].definitions[0].value.find_variable("arguments");
assert.strictEqual(symbol.global, false);
assert.strictEqual(symbol.scope, ast. // From ast
body[1]. // Select 2nd statement (equals to `var f ...`)
definitions[0]. // First definition of selected statement
value // Select function as scope
);
});
});

View File

@@ -0,0 +1,45 @@
var UglifyJS = require('../../');
var assert = require("assert");
describe("comment filters", function() {
it("Should be able to filter comments by passing regex", function() {
var ast = UglifyJS.parse("/*!test1*/\n/*test2*/\n//!test3\n//test4\n<!--test5\n<!--!test6\n-->test7\n-->!test8");
assert.strictEqual(ast.print_to_string({comments: /^!/}), "/*!test1*/\n//!test3\n//!test6\n//!test8\n");
});
it("Should be able to filter comments by passing a function", function() {
var ast = UglifyJS.parse("/*TEST 123*/\n//An other comment\n//8 chars.");
var f = function(node, comment) {
return comment.value.length === 8;
};
assert.strictEqual(ast.print_to_string({comments: f}), "/*TEST 123*/\n//8 chars.\n");
});
it("Should be able to get the comment and comment type when using a function", function() {
var ast = UglifyJS.parse("/*!test1*/\n/*test2*/\n//!test3\n//test4\n<!--test5\n<!--!test6\n-->test7\n-->!test8");
var f = function(node, comment) {
return comment.type == "comment1" || comment.type == "comment3";
};
assert.strictEqual(ast.print_to_string({comments: f}), "//!test3\n//test4\n//test5\n//!test6\n");
});
it("Should be able to filter comments by passing a boolean", function() {
var ast = UglifyJS.parse("/*!test1*/\n/*test2*/\n//!test3\n//test4\n<!--test5\n<!--!test6\n-->test7\n-->!test8");
assert.strictEqual(ast.print_to_string({comments: true}), "/*!test1*/\n/*test2*/\n//!test3\n//test4\n//test5\n//!test6\n//test7\n//!test8\n");
assert.strictEqual(ast.print_to_string({comments: false}), "");
});
it("Should never be able to filter comment5 (shebangs)", function() {
var ast = UglifyJS.parse("#!Random comment\n//test1\n/*test2*/");
var f = function(node, comment) {
assert.strictEqual(comment.type === "comment5", false);
return true;
};
assert.strictEqual(ast.print_to_string({comments: f}), "#!Random comment\n//test1\n/*test2*/\n");
});
});

View File

@@ -0,0 +1,89 @@
var UglifyJS = require('../../');
var assert = require("assert");
describe("Getters and setters", function() {
it("Should not accept operator symbols as getter/setter name", function() {
var illegalOperators = [
"++",
"--",
"+",
"-",
"!",
"~",
"&",
"|",
"^",
"*",
"/",
"%",
">>",
"<<",
">>>",
"<",
">",
"<=",
">=",
"==",
"===",
"!=",
"!==",
"?",
"=",
"+=",
"-=",
"/=",
"*=",
"%=",
">>=",
"<<=",
">>>=",
"|=",
"^=",
"&=",
"&&",
"||"
];
var generator = function() {
var results = [];
for (var i in illegalOperators) {
results.push({
code: "var obj = { get " + illegalOperators[i] + "() { return test; }};",
operator: illegalOperators[i],
method: "get"
});
results.push({
code: "var obj = { set " + illegalOperators[i] + "(value) { test = value}};",
operator: illegalOperators[i],
method: "set"
});
}
return results;
};
var testCase = function(data) {
return function() {
UglifyJS.parse(data.code);
};
};
var fail = function(data) {
return function (e) {
return e instanceof UglifyJS.JS_Parse_Error &&
e.message === "Invalid getter/setter name: " + data.operator;
};
};
var errorMessage = function(data) {
return "Expected but didn't get a syntax error while parsing following line:\n" + data.code;
};
var tests = generator();
for (var i = 0; i < tests.length; i++) {
var test = tests[i];
assert.throws(testCase(test), fail(test), errorMessage(test));
}
});
});

View File

@@ -0,0 +1,19 @@
var Uglify = require('../../');
var assert = require("assert");
describe("Huge number of comments.", function() {
it("Should parse and compress code with thousands of consecutive comments", function() {
var js = 'function lots_of_comments(x) { return 7 -';
var i;
for (i = 1; i <= 5000; ++i) { js += "// " + i + "\n"; }
for (; i <= 10000; ++i) { js += "/* " + i + " */ /**/"; }
js += "x; }";
var result = Uglify.minify(js, {
fromString: true,
mangle: false,
compress: {}
});
assert.strictEqual(result.code, "function lots_of_comments(x){return 7-x}");
});
});

30
test/mocha/let.js Normal file
View File

@@ -0,0 +1,30 @@
var Uglify = require('../../');
var assert = require("assert");
describe("let", function() {
it("Should not produce `let` as a variable name in mangle", function(done) {
this.timeout(10000);
// Produce a lot of variables in a function and run it through mangle.
var s = '"use strict"; function foo() {';
for (var i = 0; i < 21000; ++i) {
s += "var v" + i + "=0;";
}
s += '}';
var result = Uglify.minify(s, {fromString: true, compress: false});
// 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);
assert.strictEqual(result.code.indexOf("var var="), -1);
// Verify that the variable names that appeared immediately before
// and after the erroneously generated `let` variable name still exist
// to show the test generated enough symbols.
assert(result.code.indexOf("var ket=") >= 0);
assert(result.code.indexOf("var met=") >= 0);
done();
});
});

View File

@@ -0,0 +1,34 @@
var UglifyJS = require('../../');
var assert = require("assert");
describe("String literals", function() {
it("Should throw syntax error if a string literal contains a newline", function() {
var inputs = [
"'\n'",
"'\r'",
'"\r\n"',
"'\u2028'",
'"\u2029"'
];
var test = function(input) {
return function() {
var ast = UglifyJS.parse(input);
};
};
var error = function(e) {
return e instanceof UglifyJS.JS_Parse_Error &&
e.message === "Unterminated string constant";
};
for (var input in inputs) {
assert.throws(test(inputs[input]), error);
}
});
it("Should not throw syntax error if a string has a line continuation", function() {
var output = UglifyJS.parse('var a = "a\\\nb";').print_to_string();
assert.equal(output, 'var a="ab";');
});
});

View File

@@ -16,6 +16,9 @@ if (failures) {
process.exit(1); process.exit(1);
} }
var mocha_tests = require("./mocha.js");
mocha_tests();
var run_sourcemaps_tests = require('./sourcemaps'); var run_sourcemaps_tests = require('./sourcemaps');
run_sourcemaps_tests(); run_sourcemaps_tests();
@@ -86,9 +89,18 @@ 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 options = U.defaults(test.options, { var options = U.defaults(test.options, {
warnings: false warnings: false
}); });
var warnings_emitted = [];
var original_warn_function = U.AST_Node.warn_function;
if (test.expect_warnings) {
U.AST_Node.warn_function = function(text) {
warnings_emitted.push("WARN: " + text);
};
options.warnings = true;
}
var cmp = new U.Compressor(options, true); var cmp = new U.Compressor(options, true);
var output_options = test.beautify || {}; var output_options = test.beautify || {};
var expect; var expect;
@@ -105,6 +117,7 @@ function run_compress_tests() {
var output = input.transform(cmp); var output = input.transform(cmp);
output.figure_out_scope(); output.figure_out_scope();
if (test.mangle) { if (test.mangle) {
output.compute_char_frequency(test.mangle);
output.mangle_names(test.mangle); output.mangle_names(test.mangle);
} }
output = make_code(output, output_options); output = make_code(output, output_options);
@@ -117,6 +130,24 @@ function run_compress_tests() {
failures++; failures++;
failed_files[file] = 1; failed_files[file] = 1;
} }
else if (test.expect_warnings) {
U.AST_Node.warn_function = original_warn_function;
var expected_warnings = make_code(test.expect_warnings, {
beautify: false,
quote_style: 2, // force double quote to match JSON
});
var actual_warnings = JSON.stringify(warnings_emitted);
actual_warnings = actual_warnings.split(process.cwd() + "/").join("");
if (expected_warnings != actual_warnings) {
log("!!! failed\n---INPUT---\n{input}\n---EXPECTED WARNINGS---\n{expected_warnings}\n---ACTUAL WARNINGS---\n{actual_warnings}\n\n", {
input: input_code,
expected_warnings: expected_warnings,
actual_warnings: actual_warnings,
});
failures++;
failed_files[file] = 1;
}
}
} }
var tests = parse_test(path.resolve(dir, file)); var tests = parse_test(path.resolve(dir, file));
for (var i in tests) if (tests.hasOwnProperty(i)) { for (var i in tests) if (tests.hasOwnProperty(i)) {
@@ -168,7 +199,7 @@ function parse_test(file) {
} }
if (node instanceof U.AST_LabeledStatement) { if (node instanceof U.AST_LabeledStatement) {
assert.ok( assert.ok(
node.label.name == "input" || node.label.name == "expect" || node.label.name == "expect_exact", ["input", "expect", "expect_exact", "expect_warnings"].indexOf(node.label.name) >= 0,
tmpl("Unsupported label {name} [{line},{col}]", { tmpl("Unsupported label {name} [{line},{col}]", {
name: node.label.name, name: node.label.name,
line: node.label.start.line, line: node.label.start.line,

View File

@@ -15,3 +15,4 @@ exports["parse"] = parse;
exports["push_uniq"] = push_uniq; exports["push_uniq"] = push_uniq;
exports["string_template"] = string_template; exports["string_template"] = string_template;
exports["is_identifier"] = is_identifier; exports["is_identifier"] = is_identifier;
exports["SymbolDef"] = SymbolDef;

View File

@@ -39,8 +39,11 @@ exports.minify = function(files, options) {
fromString : false, fromString : false,
warnings : false, warnings : false,
mangle : {}, mangle : {},
mangleProperties : false,
nameCache : null,
output : null, output : null,
compress : {} compress : {},
parse : {}
}); });
UglifyJS.base54.reset(); UglifyJS.base54.reset();
@@ -60,7 +63,8 @@ exports.minify = function(files, options) {
sourcesContent[file] = code; sourcesContent[file] = code;
toplevel = UglifyJS.parse(code, { toplevel = UglifyJS.parse(code, {
filename: options.fromString ? i : file, filename: options.fromString ? i : file,
toplevel: toplevel toplevel: toplevel,
bare_returns: options.parse ? options.parse.bare_returns : undefined
}); });
}); });
} }
@@ -77,14 +81,21 @@ exports.minify = function(files, options) {
toplevel = toplevel.transform(sq); toplevel = toplevel.transform(sq);
} }
// 3. mangle // 3. mangle properties
if (options.mangleProperties || options.nameCache) {
options.mangleProperties.cache = UglifyJS.readNameCache(options.nameCache, "props");
toplevel = UglifyJS.mangle_properties(toplevel, options.mangleProperties);
UglifyJS.writeNameCache(options.nameCache, "props", options.mangleProperties.cache);
}
// 4. mangle
if (options.mangle) { if (options.mangle) {
toplevel.figure_out_scope(options.mangle); toplevel.figure_out_scope(options.mangle);
toplevel.compute_char_frequency(options.mangle); toplevel.compute_char_frequency(options.mangle);
toplevel.mangle_names(options.mangle); toplevel.mangle_names(options.mangle);
} }
// 4. output // 5. output
var inMap = options.inSourceMap; var inMap = options.inSourceMap;
var output = {}; var output = {};
if (typeof options.inSourceMap == "string") { if (typeof options.inSourceMap == "string") {