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,
-b or -m.
--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
code relying on Function.prototype.name.
--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
(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
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`
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
declarations etc.
@@ -395,6 +395,8 @@ separate file and include it into the build. For example you can have a
```javascript
const DEBUG = false;
const PRODUCTION = true;
// Alternative for environments that don't support `const`
/** @const */ var STAGING = false;
// etc.
```
@@ -404,10 +406,26 @@ and build your code like this:
UglifyJS will notice the constants and, since they cannot be altered, it
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
will contain the `const` declarations.
code as usual. The build will contain the `const` declarations if you use
them. If you are targeting < ES6 environments, use `/** @const */ var`.
<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
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.
- `mangleProperties` (default `false`) — pass an object to specify custom
mangle property options.
- `output` (default `null`) — pass an object if you wish to specify
additional [output options][codegen]. The defaults are optimized
for best compression.
@@ -631,6 +652,13 @@ Other options:
- `compress` (default `{}`) — pass `false` to skip compressing entirely.
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
functionality please suggest!
@@ -649,6 +677,9 @@ properties are available:
- `strict` — disable automatic semicolon insertion and support for trailing
comma in arrays and objects
- `bare_returns` — Allow return outside of functions. (maps to the
`--bare-returns` CLI arguments option and available to `minify` `parse`
other options object)
- `filename` — the name of the file where this code is coming from
- `toplevel` — a `toplevel` node (as returned by a previous invocation of
`parse`)
@@ -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
[codegen]: http://lisperator.net/uglifyjs/codegen
[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) {
x = ARGS[x];
if (x == null) return null;
function getOptions(flag, constants) {
var x = ARGS[flag];
if (x == null || x === false) return null;
var ret = {};
if (x !== "") {
if (Array.isArray(x)) x = x.map(function (v) { return "(" + v + ")"; }).join(", ");
var ast;
try {
ast = UglifyJS.parse(x, { expression: true });
} catch(ex) {
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);
}
}
@@ -529,7 +531,7 @@ function getOptions(x, constants) {
return true; // no descend
}
print_error(node.TYPE)
print_error("Error parsing arguments in: " + x);
print_error("Error parsing arguments for flag `" + flag + "': " + x);
process.exit(1);
}));
}

View File

@@ -71,7 +71,7 @@ function DEFNODE(type, props, methods, base) {
if (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)) {
ctor[i.substr(1)] = methods[i];
} 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",
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)",
},
}
}, AST_Block);
var AST_Toplevel = DEFNODE("Toplevel", "globals", {
@@ -849,6 +849,13 @@ var AST_Seq = DEFNODE("Seq", "car cdr", {
p = p.cdr;
}
},
len: function() {
if (this.cdr instanceof AST_Seq) {
return this.cdr.len() + 1;
} else {
return 2;
}
},
_walk: function(visitor) {
return visitor._visit(this, function(){
this.car._walk(visitor);

View File

@@ -66,6 +66,7 @@ function Compressor(options, false_by_default) {
hoist_vars : false,
if_return : !false_by_default,
join_vars : !false_by_default,
collapse_vars : false,
cascade : !false_by_default,
side_effects : !false_by_default,
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) {
if (thing === null) return [];
if (thing instanceof AST_BlockStatement) return thing.body;
@@ -226,6 +244,9 @@ merge(Compressor.prototype, {
if (compressor.option("join_vars")) {
statements = join_consecutive_vars(statements, compressor);
}
if (compressor.option("collapse_vars")) {
statements = collapse_single_use_vars(statements, compressor);
}
} while (CHANGED && max_iter-- > 0);
if (compressor.option("negate_iife")) {
@@ -234,6 +255,163 @@ merge(Compressor.prototype, {
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 has_inject(comment) {
return /@ngInject/.test(comment.value);
@@ -511,8 +689,12 @@ merge(Compressor.prototype, {
seq = [];
};
statements.forEach(function(stat){
if (stat instanceof AST_SimpleStatement && seq.length < 2000) seq.push(stat.body);
else push_seq(), ret.push(stat);
if (stat instanceof AST_SimpleStatement && seqLength(seq) < 2000) {
seq.push(stat.body);
} else {
push_seq();
ret.push(stat);
}
});
push_seq();
ret = sequencesize_2(ret, compressor);
@@ -520,6 +702,18 @@ merge(Compressor.prototype, {
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 cons_seq(right) {
ret.pop();
@@ -639,7 +833,9 @@ merge(Compressor.prototype, {
};
function extract_declarations_from_unreachable_code(compressor, stat, target) {
if (!(stat instanceof AST_Defun)) {
compressor.warn("Dropping unreachable code [{file}:{line},{col}]", stat.start);
}
stat.walk(new TreeWalker(function(node){
if (node instanceof AST_Var) {
compressor.warn("Declarations in unreachable code! [{file}:{line},{col}]", node.start);
@@ -854,8 +1050,16 @@ merge(Compressor.prototype, {
: ev(this.alternative, compressor);
});
def(AST_SymbolRef, function(compressor){
if (this._evaluating) throw def;
this._evaluating = true;
try {
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;
});
def(AST_Dot, function(compressor){
@@ -1092,13 +1296,14 @@ merge(Compressor.prototype, {
&& !self.uses_eval
) {
var in_use = [];
var in_use_ids = {}; // avoid expensive linear scans of in_use
var initializations = new Dictionary();
// pass 1: find out which symbols are directly used in
// this scope (not in nested scopes).
var scope = this;
var tw = new TreeWalker(function(node, descend){
if (node !== self) {
if (node instanceof AST_Defun) {
if (node instanceof AST_Defun || node instanceof AST_DefClass) {
initializations.add(node.name.name, node);
return true; // don't go in nested scopes
}
@@ -1115,7 +1320,11 @@ merge(Compressor.prototype, {
return true;
}
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;
}
if (node instanceof AST_Scope) {
@@ -1138,7 +1347,11 @@ merge(Compressor.prototype, {
if (init) init.forEach(function(init){
var tw = new TreeWalker(function(node){
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);
@@ -1178,9 +1391,7 @@ merge(Compressor.prototype, {
}
}
if ((node instanceof AST_Defun || node instanceof AST_DefClass) && node !== self) {
var keep =
member(node.name.definition(), in_use) ||
node.name.definition().global;
var keep = (node.name.definition().id in in_use_ids) || node.name.definition().global;
if (!keep) {
compressor.warn("Dropping unused function {name} [{file}:{line},{col}]", {
name : node.name.name,
@@ -1195,8 +1406,9 @@ merge(Compressor.prototype, {
if (node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn)) {
var def = node.definitions.filter(function(def){
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;
var w = {
name : def.name.name,
file : def.name.start.file,
@@ -1333,7 +1545,10 @@ merge(Compressor.prototype, {
var seq = node.to_assignments();
var p = tt.parent();
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;
}
if (p instanceof AST_For && p.init === node) {
@@ -1564,9 +1779,13 @@ merge(Compressor.prototype, {
}
if (is_empty(self.alternative)) self.alternative = null;
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) {
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;
var tmp = self.body;
self.body = self.alternative || make_node(AST_EmptyStatement);
@@ -1588,6 +1807,13 @@ merge(Compressor.prototype, {
}).transform(compressor);
}
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, {
body: make_node(AST_Binary, self, {
operator : "||",
@@ -2020,13 +2246,7 @@ merge(Compressor.prototype, {
if (!compressor.option("side_effects"))
return self;
if (!self.car.has_side_effects(compressor)) {
// we shouldn't compress (1,func)(something) to
// 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;
}
return maintain_this_binding(compressor.parent(), self, self.cdr);
}
if (compressor.option("cascade")) {
if (self.car instanceof AST_Assign
@@ -2216,11 +2436,10 @@ merge(Compressor.prototype, {
if (ll.length > 1) {
if (ll[1]) {
compressor.warn("Condition left of && always true [{file}:{line},{col}]", self.start);
var rr = self.right.evaluate(compressor);
return rr[0];
return maintain_this_binding(compressor.parent(), self, self.right.evaluate(compressor)[0]);
} else {
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[1]) {
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 {
compressor.warn("Condition left of || always false [{file}:{line},{col}]", self.start);
var rr = self.right.evaluate(compressor);
return rr[0];
return maintain_this_binding(compressor.parent(), self, self.right.evaluate(compressor)[0]);
}
}
}
@@ -2392,7 +2610,7 @@ merge(Compressor.prototype, {
if (self.undeclared() && !isLHS(self, compressor.parent())) {
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);
}
switch (self.name) {
@@ -2458,10 +2676,10 @@ merge(Compressor.prototype, {
if (cond.length > 1) {
if (cond[1]) {
compressor.warn("Condition always true [{file}:{line},{col}]", self.start);
return self.consequent;
return maintain_this_binding(compressor.parent(), self, self.consequent);
} else {
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);
@@ -2541,20 +2759,58 @@ merge(Compressor.prototype, {
}
}
// y?true:false --> !!y
if (is_true(consequent) && is_false(alternative)) {
self.condition = self.condition.negate(compressor);
return make_node(AST_UnaryPrefix, self.condition, {
operator: "!",
expression: self.condition
if (is_true(self.consequent)) {
if (is_false(self.alternative)) {
// c ? true : false ---> !!c
return booleanize(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(consequent) && is_true(alternative)) {
return self.condition.negate(compressor)
if (is_false(self.consequent)) {
if (is_true(self.alternative)) {
// 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;
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
function is_true(node) {
return node instanceof AST_True

View File

@@ -74,7 +74,7 @@ function OutputStream(options) {
var OUTPUT = "";
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);
if (code.length <= 2 && !identifier) {
while (code.length < 2) code = "0" + code;
@@ -90,16 +90,17 @@ function OutputStream(options) {
var dq = 0, sq = 0;
str = str.replace(/[\\\b\f\n\r\v\t\x22\x27\u2028\u2029\0\ufeff]/g, function(s){
switch (s) {
case '"': ++dq; return '"';
case "'": ++sq; return "'";
case "\\": return "\\\\";
case "\b": return "\\b";
case "\f": return "\\f";
case "\n": return "\\n";
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 "\u2028": return "\\u2028";
case "\u2029": return "\\u2029";
case '"': ++dq; return '"';
case "'": ++sq; return "'";
case "\0": return "\\x00";
case "\ufeff": return "\\ufeff";
}
@@ -449,11 +450,11 @@ function OutputStream(options) {
});
} else if (c.test) {
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") {
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){
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;
}
}
});
PARENS([ AST_Assign, AST_Conditional ], function (output){
@@ -1180,7 +1185,7 @@ function OutputStream(options) {
var expr = self.expression;
expr.print(output);
if (expr instanceof AST_Number && expr.getValue() >= 0) {
if (!/[xa-f.]/i.test(output.last())) {
if (!/[xa-f.)]/i.test(output.last())) {
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_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;
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));
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;
ret += ch;
}
@@ -431,7 +431,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
S.col = S.tokcol + (S.pos - S.tokpos);
S.comments_before.push(token(type, ret, true));
S.regex_allowed = regex_allowed;
return next_token();
return next_token;
};
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.regex_allowed = regex_allowed;
S.newline_before = nlb;
return next_token();
return next_token;
});
function read_name() {
@@ -575,16 +575,19 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
function next_token(force_regexp) {
if (force_regexp != null)
return read_regexp(force_regexp);
for (;;) {
skip_whitespace();
start_token();
if (html5_comments) {
if (looking_at("<!--")) {
forward(4);
return skip_line_comment("comment3");
skip_line_comment("comment3");
continue;
}
if (looking_at("-->") && S.newline_before) {
forward(3);
return skip_line_comment("comment4");
skip_line_comment("comment4");
continue;
}
}
var ch = peek();
@@ -593,20 +596,26 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
switch (code) {
case 34: case 39: return read_string(ch);
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();
}
if (is_digit(code)) return read_num();
if (PUNC_CHARS(ch)) return token("punc", next());
if (OPERATOR_CHARS(ch)) return read_operator();
if (code == 92 || is_identifier_start(code)) return read_word();
if (shebang) {
if (S.pos == 0 && looking_at("#!")) {
forward(2);
return skip_line_comment("comment5");
skip_line_comment("comment5");
continue;
}
}
break;
}
parse_error("Unexpected character '" + ch + "'");
};
@@ -1379,6 +1388,13 @@ function parse($TEXT, options) {
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();
return ret;

View File

@@ -55,8 +55,11 @@ function SymbolDef(scope, index, orig) {
this.undeclared = false;
this.constant = false;
this.index = index;
this.id = SymbolDef.next_id++;
};
SymbolDef.next_id = 1;
SymbolDef.prototype = {
unmangleable: function(options) {
if (!options) options = {};
@@ -102,6 +105,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
var scope = self.parent_scope = null;
var labels = new Dictionary();
var defun = null;
var last_var_had_const_pragma = false;
var nesting = 0;
var in_destructuring = null;
var in_export;
@@ -195,11 +199,14 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
// inside the class.
(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
|| node instanceof AST_SymbolConst
|| node instanceof AST_SymbolLet) {
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.init = tw.parent().value;
}
@@ -244,6 +251,11 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
}
if (node instanceof AST_SymbolRef) {
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);
if (!sym) {
var g;
@@ -256,10 +268,6 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
globals.set(name, 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") {
func.uses_arguments = true;
}
@@ -299,6 +307,10 @@ AST_Block.DEFMETHOD("creates_block_scope", function() {
AST_Lambda.DEFMETHOD("init_scope_vars", function(){
AST_Scope.prototype.init_scope_vars.apply(this, arguments);
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() {
@@ -420,11 +432,17 @@ AST_Symbol.DEFMETHOD("global", function(){
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){
return defaults(options, {
except : [],
eval : false,
sort : false,
sort : false, // Ignored. Flag retained for backwards compatibility.
toplevel : false,
screw_ie8 : false,
keep_fnames : false,
@@ -434,6 +452,10 @@ AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){
AST_Toplevel.DEFMETHOD("mangle_names", function(options){
options = this._default_mangler_options(options);
// Never mangle arguments
options.except.push('arguments');
// We only need to mangle declaration nodes. Special logic wired
// 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
@@ -464,9 +486,6 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
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);
return;
}

View File

@@ -53,16 +53,11 @@ function SourceMap(options) {
orig_line_diff : 0,
dest_line_diff : 0,
});
var orig_map = options.orig && new MOZ_SourceMap.SourceMapConsumer(options.orig);
var generator;
if (orig_map) {
generator = MOZ_SourceMap.SourceMapGenerator.fromSourceMap(orig_map);
} else {
generator = new MOZ_SourceMap.SourceMapGenerator({
var generator = new MOZ_SourceMap.SourceMapGenerator({
file : options.file,
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) {
if (orig_map) {
var info = orig_map.originalPositionFor({
@@ -83,7 +78,7 @@ function SourceMap(options) {
source : source,
name : name
});
}
};
return {
add : add,
get : function() { return generator },

View File

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

View File

@@ -59,10 +59,7 @@ function characters(str) {
};
function member(name, array) {
for (var i = array.length; --i >= 0;)
if (array[i] == name)
return true;
return false;
return array.indexOf(name) >= 0;
};
function find_if(func, array) {
@@ -97,17 +94,17 @@ function defaults(args, defs, croak) {
if (args === true)
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);
for (var i in defs) if (defs.hasOwnProperty(i)) {
ret[i] = (args && args.hasOwnProperty(i)) ? args[i] : defs[i];
for (var i in defs) if (HOP(defs, i)) {
ret[i] = (args && HOP(args, i)) ? args[i] : defs[i];
}
return ret;
};
function merge(obj, ext) {
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];
count++;
}
@@ -150,7 +147,7 @@ var MAP = (function(){
}
}
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);
};
@@ -308,3 +305,7 @@ Dictionary.fromObject = function(obj) {
dict._size = merge(dict._values, obj);
return dict;
};
function HOP(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}

View File

@@ -4,7 +4,7 @@
"homepage": "http://lisperator.net/uglifyjs",
"author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
"license": "BSD-2-Clause",
"version": "2.6.1",
"version": "2.6.2",
"engines": {
"node": ">=0.8.0"
},
@@ -38,7 +38,8 @@
"acorn": "~0.6.0",
"escodegen": "~1.3.3",
"esfuzz": "~0.3.1",
"estraverse": "~1.5.1"
"estraverse": "~1.5.1",
"mocha": "~2.3.4"
},
"browserify": {
"transform": [
@@ -48,5 +49,6 @@
"scripts": {
"shrinkwrap": "rm ./npm-shrinkwrap.json; rm -rf ./node_modules; npm i && npm shrinkwrap && npm outdated",
"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 ? 1 : false;
a = condition ? 0 : true;
a = !!condition && 1;
a = !condition || 0;
a = condition ? 1 : 0;
}
}
@@ -490,8 +490,8 @@ cond_8b: {
a = !condition;
a = !condition;
a = condition ? 1 : !1;
a = condition ? 0 : !0;
a = !!condition && 1;
a = !condition || 0;
a = condition ? 1 : 0;
}
}
@@ -557,7 +557,7 @@ cond_8c: {
a = !!condition;
a = !condition;
a = condition() ? !0 : !-3.5;
a = !!condition() || !-3.5;
a = !!condition;
a = !!condition;
@@ -573,12 +573,68 @@ cond_8c: {
a = !condition;
a = !condition;
a = condition ? 1 : false;
a = condition ? 0 : true;
a = !!condition && 1;
a = !condition || 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: {
options = {
conditionals: true,
@@ -738,3 +794,77 @@ conditional_or: {
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);
}
}
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 () {} } }
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: {
options = { side_effects: true };
input: {
(0, 1, eval)();
(0, 1, logThis)();
(0, 1, _decorators.logThis)();
}
expect: {
(0, logThis)();
(0, eval)();
logThis();
(0, _decorators.logThis)();
}
}
dont_remove_lexical_binding_sequence: {
dont_remove_this_binding_sequence: {
options = { side_effects: true };
input: {
(0, eval)();
(0, logThis)();
(0, _decorators.logThis)();
}
expect: {
(0, logThis)();
(0, eval)();
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);
}
var mocha_tests = require("./mocha.js");
mocha_tests();
var run_sourcemaps_tests = require('./sourcemaps');
run_sourcemaps_tests();
@@ -86,9 +89,18 @@ function run_compress_tests() {
log_start_file(file);
function test_case(test) {
log_test(test.name);
U.base54.reset();
var options = U.defaults(test.options, {
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 output_options = test.beautify || {};
var expect;
@@ -105,6 +117,7 @@ function run_compress_tests() {
var output = input.transform(cmp);
output.figure_out_scope();
if (test.mangle) {
output.compute_char_frequency(test.mangle);
output.mangle_names(test.mangle);
}
output = make_code(output, output_options);
@@ -117,6 +130,24 @@ function run_compress_tests() {
failures++;
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));
for (var i in tests) if (tests.hasOwnProperty(i)) {
@@ -168,7 +199,7 @@ function parse_test(file) {
}
if (node instanceof U.AST_LabeledStatement) {
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}]", {
name: node.label.name,
line: node.label.start.line,

View File

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

View File

@@ -39,8 +39,11 @@ exports.minify = function(files, options) {
fromString : false,
warnings : false,
mangle : {},
mangleProperties : false,
nameCache : null,
output : null,
compress : {}
compress : {},
parse : {}
});
UglifyJS.base54.reset();
@@ -60,7 +63,8 @@ exports.minify = function(files, options) {
sourcesContent[file] = code;
toplevel = UglifyJS.parse(code, {
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);
}
// 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) {
toplevel.figure_out_scope(options.mangle);
toplevel.compute_char_frequency(options.mangle);
toplevel.mangle_names(options.mangle);
}
// 4. output
// 5. output
var inMap = options.inSourceMap;
var output = {};
if (typeof options.inSourceMap == "string") {