diff --git a/README.md b/README.md
index 67324dbd..92559019 100644
--- a/README.md
+++ b/README.md
@@ -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,8 +406,8 @@ 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`.
## Beautifier options
@@ -624,6 +626,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 +636,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 +661,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 +803,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
diff --git a/bin/uglifyjs b/bin/uglifyjs
index f7f22215..90197cc4 100755
--- a/bin/uglifyjs
+++ b/bin/uglifyjs
@@ -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);
}));
}
diff --git a/lib/compress.js b/lib/compress.js
index f96fb04a..9d613797 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -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;
@@ -218,6 +236,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")) {
@@ -226,6 +247,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);
@@ -1276,7 +1454,8 @@ merge(Compressor.prototype, {
var hoist_funs = compressor.option("hoist_funs");
var hoist_vars = compressor.option("hoist_vars");
-
+ var self = this;
+ if (!(self.body instanceof Array)) { return self; } // Hoisting makes no sense in an arrow func
if (hoist_funs || hoist_vars) {
var dirs = [];
var hoisted = [];
@@ -1312,7 +1491,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) {
@@ -1543,9 +1725,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);
@@ -1567,6 +1753,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 : "||",
@@ -1999,13 +2192,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
@@ -2195,11 +2382,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]);
}
}
}
@@ -2208,11 +2394,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]);
}
}
}
@@ -2437,10 +2622,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);
@@ -2522,6 +2707,10 @@ merge(Compressor.prototype, {
// y?true:false --> !!y
if (is_true(consequent) && is_false(alternative)) {
+ if (self.condition.is_boolean()) {
+ // boolean_expression ? true : false --> boolean_expression
+ return self.condition;
+ }
self.condition = self.condition.negate(compressor);
return make_node(AST_UnaryPrefix, self.condition, {
operator: "!",
diff --git a/lib/output.js b/lib/output.js
index cbd13893..53a3a8d1 100644
--- a/lib/output.js
+++ b/lib/output.js
@@ -444,11 +444,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);
});
}
@@ -596,8 +596,12 @@ function OutputStream(options) {
PARENS(AST_Number, function(output){
var p = output.parent();
- if (this.getValue() < 0 && p instanceof AST_PropAccess && p.expression === this)
- return true;
+ 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){
@@ -1172,7 +1176,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(".");
}
}
diff --git a/lib/parse.js b/lib/parse.js
index 6bc79215..2230f4e0 100644
--- a/lib/parse.js
+++ b/lib/parse.js
@@ -408,7 +408,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;
}
@@ -1367,6 +1367,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;
diff --git a/lib/scope.js b/lib/scope.js
index f9241f2d..7c3f0e02 100644
--- a/lib/scope.js
+++ b/lib/scope.js
@@ -102,6 +102,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;
@@ -165,6 +166,10 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
node.thedef = node;
node.references = [];
}
+ if (node instanceof AST_SymbolFunarg) {
+ node.object_destructuring_arg = !!in_destructuring;
+ defun.def_variable(node, in_export);
+ }
if (node instanceof AST_SymbolLambda) {
defun.def_function(node, in_export);
}
@@ -235,6 +240,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;
@@ -269,6 +279,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
});
AST_Scope.DEFMETHOD("init_scope_vars", function(nesting){
+ this.directives = []; // contains the directives defined in this scope, i.e. "use strict"
this.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions)
this.functions = new Dictionary(); // map name to AST_SymbolDefun (functions defined in this scope)
this.uses_with = false; // will be set to true if this or some nested scope uses the `with` statement
@@ -279,9 +290,17 @@ AST_Scope.DEFMETHOD("init_scope_vars", function(nesting){
this.nesting = nesting; // the nesting level of this scope (0 means toplevel)
});
+AST_Scope.DEFMETHOD("strict", function(){
+ return this.has_directive("use strict");
+});
+
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() {
@@ -410,11 +429,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,
@@ -424,6 +449,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
@@ -454,9 +483,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;
}
diff --git a/lib/sourcemap.js b/lib/sourcemap.js
index a67011f0..e5d7df60 100644
--- a/lib/sourcemap.js
+++ b/lib/sourcemap.js
@@ -53,16 +53,11 @@ function SourceMap(options) {
orig_line_diff : 0,
dest_line_diff : 0,
});
+ var generator = new MOZ_SourceMap.SourceMapGenerator({
+ file : options.file,
+ sourceRoot : options.root
+ });
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({
- file : options.file,
- sourceRoot : options.root
- });
- }
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 },
diff --git a/lib/transform.js b/lib/transform.js
index dc3a068f..34663351 100644
--- a/lib/transform.js
+++ b/lib/transform.js
@@ -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;
diff --git a/package.json b/package.json
index 6b0d2f40..748e04fb 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"homepage": "http://lisperator.net/uglifyjs",
"author": "Mihai Bazon (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"]
}
diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js
new file mode 100644
index 00000000..934a5c73
--- /dev/null
+++ b/test/compress/collapse_vars.js
@@ -0,0 +1,1155 @@
+collapse_vars_side_effects_1: {
+ 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() {
+ var e = 7;
+ var s = "abcdef";
+ var i = 2;
+ var log = console.log.bind(console);
+ var x = s.charAt(i++);
+ var y = s.charAt(i++);
+ var z = s.charAt(i++);
+ log(x, y, z, e);
+ }
+ function f2() {
+ var e = 7;
+ var log = console.log.bind(console);
+ var s = "abcdef";
+ var i = 2;
+ var x = s.charAt(i++);
+ var y = s.charAt(i++);
+ var z = s.charAt(i++);
+ log(x, i, y, z, e);
+ }
+ function f3() {
+ var e = 7;
+ var s = "abcdef";
+ var i = 2;
+ var log = console.log.bind(console);
+ var x = s.charAt(i++);
+ var y = s.charAt(i++);
+ var z = s.charAt(i++);
+ log(x, z, y, e);
+ }
+ function f4() {
+ var log = console.log.bind(console),
+ i = 10,
+ x = i += 2,
+ y = i += 3,
+ z = i += 4;
+ log(x, z, y, i);
+ }
+ }
+ expect: {
+ function f1() {
+ var s = "abcdef", i = 2;
+ console.log.bind(console)(s.charAt(i++), s.charAt(i++), s.charAt(i++), 7);
+ }
+ function f2() {
+ var log = console.log.bind(console),
+ s = "abcdef",
+ i = 2,
+ x = s.charAt(i++),
+ y = s.charAt(i++),
+ z = s.charAt(i++);
+ log(x, i, y, z, 7);
+ }
+ function f3() {
+ var s = "abcdef",
+ i = 2,
+ log = console.log.bind(console),
+ x = s.charAt(i++),
+ y = s.charAt(i++);
+ log(x, s.charAt(i++), y, 7);
+ }
+ function f4() {
+ var log = console.log.bind(console),
+ i = 10,
+ x = i += 2,
+ y = i += 3;
+ log(x, i += 4, y, i);
+ }
+ }
+}
+
+collapse_vars_side_effects_2: {
+ 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 fn(x) { return console.log(x), x; }
+
+ function p1() { var a = foo(), b = bar(), c = baz(); return a + b + c; }
+ function p2() { var a = foo(), c = bar(), b = baz(); return a + b + c; }
+ function p3() { var b = foo(), a = bar(), c = baz(); return a + b + c; }
+ function p4() { var b = foo(), c = bar(), a = baz(); return a + b + c; }
+ function p5() { var c = foo(), a = bar(), b = baz(); return a + b + c; }
+ function p6() { var c = foo(), b = bar(), a = baz(); return a + b + c; }
+
+ function q1() { var a = foo(), b = bar(), c = baz(); return fn(a + b + c); }
+ function q2() { var a = foo(), c = bar(), b = baz(); return fn(a + b + c); }
+ function q3() { var b = foo(), a = bar(), c = baz(); return fn(a + b + c); }
+ function q4() { var b = foo(), c = bar(), a = baz(); return fn(a + b + c); }
+ function q5() { var c = foo(), a = bar(), b = baz(); return fn(a + b + c); }
+ function q6() { var c = foo(), b = bar(), a = baz(); return fn(a + b + c); }
+
+ function r1() { var a = foo(), b = bar(), c = baz(); return fn(a) + fn(b) + fn(c); }
+ function r2() { var a = foo(), c = bar(), b = baz(); return fn(a) + fn(b) + fn(c); }
+ function r3() { var b = foo(), a = bar(), c = baz(); return fn(a) + fn(b) + fn(c); }
+ function r4() { var b = foo(), c = bar(), a = baz(); return fn(a) + fn(b) + fn(c); }
+ function r5() { var c = foo(), a = bar(), b = baz(); return fn(a) + fn(b) + fn(c); }
+ function r6() { var c = foo(), b = bar(), a = baz(); return fn(a) + fn(b) + fn(c); }
+
+ function s1() { var a = foo(), b = bar(), c = baz(); return g(a + b + c); }
+ function s6() { var c = foo(), b = bar(), a = baz(); return g(a + b + c); }
+
+ function t1() { var a = foo(), b = bar(), c = baz(); return g(a) + g(b) + g(c); }
+ function t6() { var c = foo(), b = bar(), a = baz(); return g(a) + g(b) + g(c); }
+ }
+ expect: {
+ function fn(x) { return console.log(x), x; }
+
+ function p1() { return foo() + bar() + baz(); }
+ function p2() { var a = foo(), c = bar(); return a + baz() + c; }
+ function p3() { var b = foo(); return bar() + b + baz(); }
+ function p4() { var b = foo(), c = bar(); return baz() + b + c; }
+ function p5() { var c = foo(); return bar() + baz() + c; }
+ function p6() { var c = foo(), b = bar(); return baz() + b + c; }
+
+ function q1() { return fn(foo() + bar() + baz()); }
+ function q2() { var a = foo(), c = bar(); return fn(a + baz() + c); }
+ function q3() { var b = foo(); return fn(bar() + b + baz()); }
+ function q4() { var b = foo(), c = bar(); return fn(baz() + b + c); }
+ function q5() { var c = foo(); return fn(bar() + baz() + c); }
+ function q6() { var c = foo(), b = bar(); return fn(baz() + b + c); }
+
+ function r1() { var a = foo(), b = bar(), c = baz(); return fn(a) + fn(b) + fn(c); }
+ function r2() { var a = foo(), c = bar(), b = baz(); return fn(a) + fn(b) + fn(c); }
+ function r3() { var b = foo(), a = bar(), c = baz(); return fn(a) + fn(b) + fn(c); }
+ function r4() { var b = foo(), c = bar(); return fn(baz()) + fn(b) + fn(c); }
+ function r5() { var c = foo(), a = bar(), b = baz(); return fn(a) + fn(b) + fn(c); }
+ function r6() { var c = foo(), b = bar(); return fn(baz()) + fn(b) + fn(c); }
+
+ function s1() { var a = foo(), b = bar(), c = baz(); return g(a + b + c); }
+ function s6() { var c = foo(), b = bar(), a = baz(); return g(a + b + c); }
+
+ function t1() { var a = foo(), b = bar(), c = baz(); return g(a) + g(b) + g(c); }
+ function t6() { var c = foo(), b = bar(), a = baz(); return g(a) + g(b) + g(c); }
+ }
+}
+
+collapse_vars_issue_721: {
+ 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: {
+ define(["require", "exports", 'handlebars'], function (require, exports, hb) {
+ var win = window;
+ var _hb = win.Handlebars = hb;
+ return _hb;
+ });
+ def(function (hb) {
+ var win = window;
+ var prop = 'Handlebars';
+ var _hb = win[prop] = hb;
+ return _hb;
+ });
+ def(function (hb) {
+ var prop = 'Handlebars';
+ var win = window;
+ var _hb = win[prop] = hb;
+ return _hb;
+ });
+ def(function (hb) {
+ var prop = 'Handlebars';
+ var win = g();
+ var _hb = win[prop] = hb;
+ return _hb;
+ });
+ def(function (hb) {
+ var prop = g1();
+ var win = g2();
+ var _hb = win[prop] = hb;
+ return _hb;
+ });
+ def(function (hb) {
+ var win = g2();
+ var prop = g1();
+ var _hb = win[prop] = hb;
+ return _hb;
+ });
+ }
+ expect: {
+ define([ "require", "exports", "handlebars" ], function(require, exports, hb) {
+ return window.Handlebars = hb;
+ }),
+ def(function(hb) {
+ return window.Handlebars = hb;
+ }),
+ def(function(hb) {
+ return window.Handlebars = hb;
+ }),
+ def(function (hb) {
+ return g().Handlebars = hb;
+ }),
+ def(function (hb) {
+ var prop = g1();
+ return g2()[prop] = hb;
+ }),
+ def(function (hb) {
+ return g2()[g1()] = hb;
+ });
+ }
+}
+
+collapse_vars_properties: {
+ 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(obj) {
+ var prop = 'LiteralProperty';
+ return !!-+obj[prop];
+ }
+ function f2(obj) {
+ var prop1 = 'One';
+ var prop2 = 'Two';
+ return ~!!-+obj[prop1 + prop2];
+ }
+ }
+ expect: {
+ function f1(obj) {
+ return !!-+obj.LiteralProperty;
+ }
+ function f2(obj) {
+ return ~!!-+obj.OneTwo;
+ }
+ }
+}
+
+collapse_vars_if: {
+ 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() {
+ var not_used = sideeffect(), x = g1 + g2;
+ var y = x / 4, z = 'Bar' + y;
+ if ('x' != z) { return g9; }
+ else return g5;
+ }
+ function f2() {
+ var x = g1 + g2, not_used = sideeffect();
+ var y = x / 4
+ var z = 'Bar' + y;
+ if ('x' != z) { return g9; }
+ else return g5;
+ }
+ function f3(x) {
+ if (x) {
+ var a = 1;
+ return a;
+ }
+ else {
+ var b = 2;
+ return b;
+ }
+ }
+ }
+ expect: {
+ function f1() {
+ sideeffect();
+ return "x" != "Bar" + (g1 + g2) / 4 ? g9 : g5;
+ }
+ function f2() {
+ var x = g1 + g2;
+ sideeffect();
+ return "x" != "Bar" + x / 4 ? g9 : g5;
+ }
+ function f3(x) {
+ if (x) {
+ return 1;
+ }
+ return 2;
+ }
+ }
+}
+
+collapse_vars_while: {
+ options = {
+ collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true,
+ comparisons:true, evaluate:true, booleans:true, loops:false, unused:true, hoist_funs:true,
+ keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true
+ }
+ input: {
+ function f1(y) {
+ // Neither the non-constant while condition `c` will be
+ // replaced, nor the non-constant `x` in the body.
+ var x = y, c = 3 - y;
+ while (c) { return x; }
+ var z = y * y;
+ return z;
+ }
+ function f2(y) {
+ // The constant `x` will be replaced in the while body.
+ var x = 7;
+ while (y) { return x; }
+ var z = y * y;
+ return z;
+ }
+ function f3(y) {
+ // The non-constant `n` will not be replaced in the while body.
+ var n = 5 - y;
+ while (y) { return n; }
+ var z = y * y;
+ return z;
+ }
+ }
+ expect: {
+ function f1(y) {
+ var x = y, c = 3 - y;
+ while (c) return x;
+ return y * y;
+ }
+ function f2(y) {
+ while (y) return 7;
+ return y * y
+ }
+ function f3(y) {
+ var n = 5 - y;
+ while (y) return n;
+ return y * y;
+ }
+ }
+}
+
+collapse_vars_do_while: {
+ options = {
+ collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true,
+ comparisons:true, evaluate:true, booleans:false, loops:false, unused:true, hoist_funs:true,
+ keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true
+ }
+ input: {
+ function f1(y) {
+ // The constant do-while condition `c` will be replaced.
+ var c = 9;
+ do { } while (c === 77);
+ }
+ function f2(y) {
+ // The non-constant do-while condition `c` will not be replaced.
+ var c = 5 - y;
+ do { } while (c);
+ }
+ function f3(y) {
+ // The constant `x` will be replaced in the do loop body.
+ function fn(n) { console.log(n); }
+ var a = 2, x = 7;
+ do {
+ fn(a = x);
+ break;
+ } while (y);
+ }
+ function f4(y) {
+ // The non-constant `a` will not be replaced in the do loop body.
+ var a = y / 4;
+ do {
+ return a;
+ } while (y);
+ }
+ function f5(y) {
+ function p(x) { console.log(x); }
+ do {
+ // The non-constant `a` will be replaced in p(a)
+ // because it is declared in same block.
+ var a = y - 3;
+ p(a);
+ } while (--y);
+ }
+ }
+ expect: {
+ function f1(y) {
+ do ; while (false);
+ }
+ function f2(y) {
+ var c = 5 - y;
+ do ; while (c);
+ }
+ function f3(y) {
+ function fn(n) { console.log(n); }
+ var a = 2;
+ do {
+ fn(a = 7);
+ break;
+ } while (y);
+ }
+ function f4(y) {
+ var a = y / 4;
+ do
+ return a;
+ while (y);
+ }
+ function f5(y) {
+ function p(x) { console.log(x); }
+ do {
+ p(y - 3);
+ } while (--y);
+ }
+ }
+}
+
+collapse_vars_seq: {
+ 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: {
+ var f1 = function(x, y) {
+ var a, b, r = x + y, q = r * r, z = q - r;
+ a = z, b = 7;
+ return a + b;
+ };
+ }
+ expect: {
+ var f1 = function(x, y) {
+ var a, b, r = x + y;
+ return a = r * r - r, b = 7, a + b
+ };
+ }
+}
+
+collapse_vars_throw: {
+ 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: {
+ var f1 = function(x, y) {
+ var a, b, r = x + y, q = r * r, z = q - r;
+ a = z, b = 7;
+ throw a + b;
+ };
+ }
+ expect: {
+ var f1 = function(x, y) {
+ var a, b, r = x + y;
+ throw a = r * r - r, b = 7, a + b
+ };
+ }
+}
+
+collapse_vars_switch: {
+ 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() {
+ var not_used = sideeffect(), x = g1 + g2;
+ var y = x / 4, z = 'Bar' + y;
+ switch (z) { case 0: return g9; }
+ }
+ function f2() {
+ var x = g1 + g2, not_used = sideeffect();
+ var y = x / 4
+ var z = 'Bar' + y;
+ switch (z) { case 0: return g9; }
+ }
+ function f3(x) {
+ switch(x) { case 1: var a = 3 - x; return a; }
+ }
+ }
+ expect: {
+ function f1() {
+ sideeffect();
+ switch ("Bar" + (g1 + g2) / 4) { case 0: return g9 }
+ }
+ function f2() {
+ var x = g1 + g2;
+ sideeffect();
+ switch ("Bar" + x / 4) { case 0: return g9 }
+ }
+ function f3(x) {
+ // verify no extraneous semicolon in case block before return
+ // when the var definition was eliminated
+ switch(x) { case 1: return 3 - x; }
+ }
+ }
+}
+
+collapse_vars_assignment: {
+ 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 log(x) { return console.log(x), x; }
+ function f0(c) {
+ var a = 3 / c;
+ return a = a;
+ }
+ function f1(c) {
+ const a = 3 / c;
+ const b = 1 - a;
+ return b;
+ }
+ function f2(c) {
+ var a = 3 / c;
+ var b = a - 7;
+ return log(c = b);
+ }
+ function f3(c) {
+ var a = 3 / c;
+ var b = a - 7;
+ return log(c |= b);
+ }
+ function f4(c) {
+ var a = 3 / c;
+ var b = 2;
+ return log(b += a);
+ }
+ function f5(c) {
+ var b = 2;
+ var a = 3 / c;
+ return log(b += a);
+ }
+ function f6(c) {
+ var b = g();
+ var a = 3 / c;
+ return log(b += a);
+ }
+ }
+ expect: {
+ function log(x) { return console.log(x), x; }
+ function f0(c) {
+ var a = 3 / c;
+ return a = a;
+ }
+ function f1(c) {
+ return 1 - 3 / c
+ }
+ function f2(c) {
+ return log(c = 3 / c - 7);
+ }
+ function f3(c) {
+ return log(c |= 3 / c - 7);
+ }
+ function f4(c) {
+ var b = 2;
+ return log(b += 3 / c);
+ }
+ function f5(c) {
+ var b = 2;
+ return log(b += 3 / c);
+ }
+ function f6(c) {
+ var b = g();
+ return log(b += 3 / c);
+ }
+ }
+}
+
+collapse_vars_lvalues: {
+ 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 f0(x) { var i = ++x; return x += i; }
+ function f1(x) { var a = (x -= 3); return x += a; }
+ function f2(x) { var z = x, a = ++z; return z += a; }
+ function f3(x) { var a = (x -= 3), b = x + a; return b; }
+ function f4(x) { var a = (x -= 3); return x + a; }
+ function f5(x) { var w = e1(), v = e2(), c = v = --x, b = w = x; return b - c; }
+ function f6(x) { var w = e1(), v = e2(), c = v = --x, b = w = x; return c - b; }
+ function f7(x) { var w = e1(), v = e2(), c = v - x, b = w = x; return b - c; }
+ function f8(x) { var w = e1(), v = e2(), b = w = x, c = v - x; return b - c; }
+ function f9(x) { var w = e1(), v = e2(), b = w = x, c = v - x; return c - b; }
+ }
+ expect: {
+ function f0(x) { var i = ++x; return x += i; }
+ function f1(x) { var a = (x -= 3); return x += a; }
+ function f2(x) { var z = x, a = ++z; return z += a; }
+ function f3(x) { var a = (x -= 3); return x + a; }
+ function f4(x) { var a = (x -= 3); return x + a; }
+ function f5(x) { var w = e1(), v = e2(), c = v = --x; return (w = x) - c; }
+ function f6(x) { var w = e1(), v = e2(); return (v = --x) - (w = x); }
+ function f7(x) { var w = e1(), v = e2(), c = v - x; return (w = x) - c; }
+ function f8(x) { var w = e1(), v = e2(); return (w = x) - (v - x); }
+ function f9(x) { var w = e1(); return e2() - x - (w = x); }
+
+ }
+}
+
+collapse_vars_misc1: {
+ 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 f0(o, a, h) {
+ var b = 3 - a;
+ var obj = o;
+ var seven = 7;
+ var prop = 'run';
+ var t = obj[prop](b)[seven] = h;
+ return t;
+ }
+ function f1(x) { var y = 5 - x; return y; }
+ function f2(x) { const z = foo(), y = z / (5 - x); return y; }
+ function f3(x) { var z = foo(), y = (5 - x) / z; return y; }
+ function f4(x) { var z = foo(), y = (5 - u) / z; return y; }
+ function f5(x) { const z = foo(), y = (5 - window.x) / z; return y; }
+ function f6() { var b = window.a * window.z; return b && zap(); }
+ function f7() { var b = window.a * window.z; return b + b; }
+ function f8() { var b = window.a * window.z; var c = b + 5; return b + c; }
+ function f9() { var b = window.a * window.z; return bar() || b; }
+ function f10(x) { var a = 5, b = 3; return a += b; }
+ function f11(x) { var a = 5, b = 3; return a += --b; }
+ }
+ expect: {
+ function f0(o, a, h) {
+ var b = 3 - a;
+ return o.run(b)[7] = h;
+ }
+ function f1(x) { return 5 - x }
+ function f2(x) { return foo() / (5 - x) }
+ function f3(x) { return (5 - x) / foo() }
+ function f4(x) { var z = foo(); return (5 - u) / z }
+ function f5(x) { const z = foo(); return (5 - window.x) / z }
+ function f6() { return window.a * window.z && zap() }
+ function f7() { var b = window.a * window.z; return b + b }
+ function f8() { var b = window.a * window.z; return b + (b + 5) }
+ function f9() { var b = window.a * window.z; return bar() || b }
+ function f10(x) { var a = 5; return a += 3; }
+ function f11(x) { var a = 5, b = 3; return a += --b; }
+ }
+}
+
+collapse_vars_self_reference: {
+ options = {
+ collapse_vars:true, unused:false,
+ sequences:true, properties:true, dead_code:true, conditionals:true,
+ comparisons:true, evaluate:true, booleans:true, loops:true, hoist_funs:true,
+ keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true
+ }
+ input: {
+ // avoid bug in self-referential declaration.
+ function f1() {
+ var self = {
+ inner: function() { return self; }
+ };
+ }
+ function f2() {
+ var self = { inner: self };
+ }
+ }
+ expect: {
+ // note: `unused` option is false
+ function f1() {
+ var self = {
+ inner: function() { return self }
+ };
+ }
+ function f2() {
+ var self = { inner: self };
+ }
+ }
+}
+
+collapse_vars_repeated: {
+ 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() {
+ var dummy = 3, a = 5, unused = 2, a = 1, a = 3;
+ return -a;
+ }
+ function f2(x) {
+ var a = 3, a = x;
+ return a;
+ }
+ (function(x){
+ var a = "GOOD" + x, e = "BAD", k = "!", e = a;
+ console.log(e + k);
+ })("!"),
+
+ (function(x){
+ var a = "GOOD" + x, e = "BAD" + x, k = "!", e = a;
+ console.log(e + k);
+ })("!");
+ }
+ expect: {
+ function f1() {
+ return -3
+ }
+ function f2(x) {
+ return x
+ }
+ (function(x){
+ var a = "GOOD" + x, e = "BAD", e = a;
+ console.log(e + "!");
+ })("!"),
+ (function(x){
+ var a = "GOOD" + x, e = "BAD" + x, e = a;
+ console.log(e + "!");
+ })("!");
+ }
+}
+
+collapse_vars_closures: {
+ 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 constant_vars_can_be_replaced_in_any_scope() {
+ var outer = 3;
+ return function() { return outer; }
+ }
+ function non_constant_vars_can_only_be_replace_in_same_scope(x) {
+ var outer = x;
+ return function() { return outer; }
+ }
+ }
+ expect: {
+ function constant_vars_can_be_replaced_in_any_scope() {
+ return function() { return 3 }
+ }
+ function non_constant_vars_can_only_be_replace_in_same_scope(x) {
+ var outer = x
+ return function() { return outer }
+ }
+ }
+}
+
+collapse_vars_unary: {
+ 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 f0(o, p) {
+ var x = o[p];
+ delete x;
+ }
+ function f1(n) {
+ var k = !!n;
+ return n > +k;
+ }
+ function f2(n) {
+ // test unary with constant
+ var k = 7;
+ return k--;
+ }
+ function f3(n) {
+ // test unary with constant
+ var k = 7;
+ return ++k;
+ }
+ function f4(n) {
+ // test unary with non-constant
+ var k = 8 - n;
+ return k--;
+ }
+ function f5(n) {
+ // test unary with non-constant
+ var k = 9 - n;
+ return ++k;
+ }
+ }
+ expect: {
+ function f0(o, p) {
+ delete o[p];
+ }
+ function f1(n) {
+ return n > +!!n
+ }
+ function f2(n) {
+ var k = 7;
+ return k--
+ }
+ function f3(n) {
+ var k = 7;
+ return ++k
+ }
+ function f4(n) {
+ var k = 8 - n;
+ return k--;
+ }
+ function f5(n) {
+ var k = 9 - n;
+ return ++k;
+ }
+ }
+}
+
+collapse_vars_try: {
+ 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() {
+ try {
+ var a = 1;
+ return a;
+ }
+ catch (ex) {
+ var b = 2;
+ return b;
+ }
+ finally {
+ var c = 3;
+ return c;
+ }
+ }
+ function f2() {
+ var t = could_throw(); // shouldn't be replaced in try block
+ try {
+ return t + might_throw();
+ }
+ catch (ex) {
+ return 3;
+ }
+ }
+ }
+ expect: {
+ function f1() {
+ try {
+ return 1;
+ }
+ catch (ex) {
+ return 2;
+ }
+ finally {
+ return 3;
+ }
+ }
+ function f2() {
+ var t = could_throw();
+ try {
+ return t + might_throw();
+ }
+ catch (ex) {
+ return 3;
+ }
+ }
+ }
+}
+
+collapse_vars_array: {
+ 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(x, y) {
+ var z = x + y;
+ return [z];
+ }
+ function f2(x, y) {
+ var z = x + y;
+ return [x, side_effect(), z];
+ }
+ function f3(x, y) {
+ var z = f(x + y);
+ return [ [3], [z, x, y], [g()] ];
+ }
+ }
+ expect: {
+ function f1(x, y) {
+ return [x + y]
+ }
+ function f2(x, y) {
+ var z = x + y
+ return [x, side_effect(), z]
+ }
+ function f3(x, y) {
+ return [ [3], [f(x + y), x, y], [g()] ]
+ }
+ }
+}
+
+collapse_vars_object: {
+ 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 f0(x, y) {
+ var z = x + y;
+ return {
+ get b() { return 7; },
+ r: z
+ };
+ }
+ function f1(x, y) {
+ var z = x + y;
+ return {
+ r: z,
+ get b() { return 7; }
+ };
+ }
+ function f2(x, y) {
+ var z = x + y;
+ var k = x - y;
+ return {
+ q: k,
+ r: g(x),
+ s: z
+ };
+ }
+ function f3(x, y) {
+ var z = f(x + y);
+ return [{
+ a: {q: x, r: y, s: z},
+ b: g()
+ }];
+ }
+ }
+ expect: {
+ function f0(x, y) {
+ var z = x + y;
+ return {
+ get b() { return 7; },
+ r: z
+ };
+ }
+ function f1(x, y) {
+ return {
+ r: x + y,
+ get b() { return 7; }
+ };
+ }
+ function f2(x, y) {
+ var z = x + y;
+ return {
+ q: x - y,
+ r: g(x),
+ s: z
+ };
+ }
+ function f3(x, y) {
+ return [{
+ a: {q: x, r: y, s: f(x + y)},
+ b: g()
+ }];
+ }
+ }
+}
+
+collapse_vars_eval_and_with: {
+ 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: {
+ // Don't attempt to collapse vars in presence of eval() or with statement.
+ (function f0() {
+ var a = 2;
+ console.log(a - 5);
+ eval("console.log(a);");
+ })();
+ (function f1() {
+ var o = {a: 1}, a = 2;
+ with (o) console.log(a);
+ })();
+ (function f2() {
+ var o = {a: 1}, a = 2;
+ return function() { with (o) console.log(a) };
+ })()();
+ }
+ expect: {
+ (function f0() {
+ var a = 2;
+ console.log(a - 5);
+ eval("console.log(a);");
+ })();
+ (function f1() {
+ var o = {a: 1}, a = 2;
+ with(o) console.log(a);
+ })();
+ (function f2() {
+ var o = {a: 1}, a = 2;
+ return function() { with (o) console.log(a) };
+ })()();
+ }
+}
+
+collapse_vars_constants: {
+ 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(x) {
+ var a = 4, b = x.prop, c = 5, d = sideeffect1(), e = sideeffect2();
+ return b + (function() { return d - a * e - c; })();
+ }
+ function f2(x) {
+ var a = 4, b = x.prop, c = 5, not_used = sideeffect1(), e = sideeffect2();
+ return b + (function() { return -a * e - c; })();
+ }
+ function f3(x) {
+ var a = 4, b = x.prop, c = 5, not_used = sideeffect1();
+ return b + (function() { return -a - c; })();
+ }
+ }
+ expect: {
+ function f1(x) {
+ var b = x.prop, d = sideeffect1(), e = sideeffect2();
+ return b + (function() { return d - 4 * e - 5; })();
+ }
+ function f2(x) {
+ var b = x.prop, e = (sideeffect1(), sideeffect2());
+ return b + (function() { return -4 * e - 5; })();
+ }
+ function f3(x) {
+ var b = x.prop;
+ sideeffect1();
+ return b + (function() { return -9; })();
+ }
+ }
+}
+
+collapse_vars_arguments: {
+ 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: {
+ var outer = function() {
+ // Do not replace `arguments` but do replace the constant `k` before it.
+ var k = 7, arguments = 5, inner = function() { console.log(arguments); }
+ inner(k, 1);
+ }
+ outer();
+ }
+ expect: {
+ (function() {
+ (function(){console.log(arguments);})(7, 1);
+ })();
+ }
+}
+
+collapse_vars_short_circuit: {
+ 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 f0(x) { var a = foo(), b = bar(); return b || x; }
+ function f1(x) { var a = foo(), b = bar(); return b && x; }
+ function f2(x) { var a = foo(), b = bar(); return x && a && b; }
+ function f3(x) { var a = foo(), b = bar(); return a && x; }
+ function f4(x) { var a = foo(), b = bar(); return a && x && b; }
+ function f5(x) { var a = foo(), b = bar(); return x || a || b; }
+ function f6(x) { var a = foo(), b = bar(); return a || x || b; }
+ function f7(x) { var a = foo(), b = bar(); return a && b && x; }
+ function f8(x,y) { var a = foo(), b = bar(); return (x || a) && (y || b); }
+ function f9(x,y) { var a = foo(), b = bar(); return (x && a) || (y && b); }
+ function f10(x,y) { var a = foo(), b = bar(); return (x - a) || (y - b); }
+ function f11(x,y) { var a = foo(), b = bar(); return (x - b) || (y - a); }
+ function f12(x,y) { var a = foo(), b = bar(); return (x - y) || (b - a); }
+ function f13(x,y) { var a = foo(), b = bar(); return (a - b) || (x - y); }
+ function f14(x,y) { var a = foo(), b = bar(); return (b - a) || (x - y); }
+ }
+ expect: {
+ function f0(x) { foo(); return bar() || x; }
+ function f1(x) { foo(); return bar() && x; }
+ function f2(x) { var a = foo(), b = bar(); return x && a && b; }
+ function f3(x) { var a = foo(); bar(); return a && x; }
+ function f4(x) { var a = foo(), b = bar(); return a && x && b; }
+ function f5(x) { var a = foo(), b = bar(); return x || a || b; }
+ function f6(x) { var a = foo(), b = bar(); return a || x || b; }
+ function f7(x) { var a = foo(), b = bar(); return a && b && x; }
+ function f8(x,y) { var a = foo(), b = bar(); return (x || a) && (y || b); }
+ function f9(x,y) { var a = foo(), b = bar(); return (x && a) || (y && b); }
+ function f10(x,y) { var a = foo(), b = bar(); return (x - a) || (y - b); }
+ function f11(x,y) { var a = foo(); return (x - bar()) || (y - a); }
+ function f12(x,y) { var a = foo(), b = bar(); return (x - y) || (b - a); }
+ function f13(x,y) { return (foo() - bar()) || (x - y); }
+ function f14(x,y) { var a = foo(); return (bar() - a) || (x - y); }
+ }
+}
+
+collapse_vars_short_circuited_conditions: {
+ options = {
+ collapse_vars: true,
+ sequences: false,
+ dead_code: true,
+ conditionals: false,
+ comparisons: false,
+ evaluate: true,
+ booleans: true,
+ loops: true,
+ unused: true,
+ hoist_funs: true,
+ keep_fargs: true,
+ if_return: false,
+ join_vars: true,
+ cascade: true,
+ side_effects: true,
+ }
+ input: {
+ function c1(x) { var a = foo(), b = bar(), c = baz(); return a ? b : c; }
+ function c2(x) { var a = foo(), b = bar(), c = baz(); return a ? c : b; }
+ function c3(x) { var a = foo(), b = bar(), c = baz(); return b ? a : c; }
+ function c4(x) { var a = foo(), b = bar(), c = baz(); return b ? c : a; }
+ function c5(x) { var a = foo(), b = bar(), c = baz(); return c ? a : b; }
+ function c6(x) { var a = foo(), b = bar(), c = baz(); return c ? b : a; }
+
+ function i1(x) { var a = foo(), b = bar(), c = baz(); if (a) return b; else return c; }
+ function i2(x) { var a = foo(), b = bar(), c = baz(); if (a) return c; else return b; }
+ function i3(x) { var a = foo(), b = bar(), c = baz(); if (b) return a; else return c; }
+ function i4(x) { var a = foo(), b = bar(), c = baz(); if (b) return c; else return a; }
+ function i5(x) { var a = foo(), b = bar(), c = baz(); if (c) return a; else return b; }
+ function i6(x) { var a = foo(), b = bar(), c = baz(); if (c) return b; else return a; }
+ }
+ expect: {
+ function c1(x) { var a = foo(), b = bar(), c = baz(); return a ? b : c; }
+ function c2(x) { var a = foo(), b = bar(), c = baz(); return a ? c : b; }
+ function c3(x) { var a = foo(), b = bar(), c = baz(); return b ? a : c; }
+ function c4(x) { var a = foo(), b = bar(), c = baz(); return b ? c : a; }
+ function c5(x) { var a = foo(), b = bar(); return baz() ? a : b; }
+ function c6(x) { var a = foo(), b = bar(); return baz() ? b : a; }
+
+ function i1(x) { var a = foo(), b = bar(), c = baz(); if (a) return b; else return c; }
+ function i2(x) { var a = foo(), b = bar(), c = baz(); if (a) return c; else return b; }
+ function i3(x) { var a = foo(), b = bar(), c = baz(); if (b) return a; else return c; }
+ function i4(x) { var a = foo(), b = bar(), c = baz(); if (b) return c; else return a; }
+ function i5(x) { var a = foo(), b = bar(); if (baz()) return a; else return b; }
+ function i6(x) { var a = foo(), b = bar(); if (baz()) return b; else return a; }
+ }
+}
+
diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js
index 65cfea64..db0d8000 100644
--- a/test/compress/conditionals.js
+++ b/test/compress/conditionals.js
@@ -738,3 +738,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));
+ }
+}
diff --git a/test/compress/dead-code.js b/test/compress/dead-code.js
index 5009ae1e..fa4b37d6 100644
--- a/test/compress/dead-code.js
+++ b/test/compress/dead-code.js
@@ -87,3 +87,120 @@ dead_code_constant_boolean_should_warn_more: {
var moo;
}
}
+
+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');
+ }
+}
diff --git a/test/compress/issue-12.js b/test/compress/issue-12.js
index bf87d5c0..e2d8bda7 100644
--- a/test/compress/issue-12.js
+++ b/test/compress/issue-12.js
@@ -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;
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/test/compress/issue-782.js b/test/compress/issue-782.js
index cce15fd1..2f72d1ab 100644
--- a/test/compress/issue-782.js
+++ b/test/compress/issue-782.js
@@ -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)();
}
}
diff --git a/test/compress/issue-892.js b/test/compress/issue-892.js
new file mode 100644
index 00000000..2dab420f
--- /dev/null
+++ b/test/compress/issue-892.js
@@ -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);"
+}
diff --git a/test/compress/issue-913.js b/test/compress/issue-913.js
new file mode 100644
index 00000000..9d34d9d9
--- /dev/null
+++ b/test/compress/issue-913.js
@@ -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;
+ })();
+ }
+}
diff --git a/test/compress/issue-973.js b/test/compress/issue-973.js
new file mode 100644
index 00000000..0e040922
--- /dev/null
+++ b/test/compress/issue-973.js
@@ -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);');
+ }());
+ }
+}
\ No newline at end of file
diff --git a/test/compress/issue-976.js b/test/compress/issue-976.js
new file mode 100644
index 00000000..dea30705
--- /dev/null
+++ b/test/compress/issue-976.js
@@ -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,o){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")}'
+}
diff --git a/test/compress/issue-979.js b/test/compress/issue-979.js
new file mode 100644
index 00000000..bae15db8
--- /dev/null
+++ b/test/compress/issue-979.js
@@ -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();
+ }
+ }
+}
+
diff --git a/test/compress/numbers.js b/test/compress/numbers.js
new file mode 100644
index 00000000..8e32ad02
--- /dev/null
+++ b/test/compress/numbers.js
@@ -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);"
+}
diff --git a/test/mocha.js b/test/mocha.js
new file mode 100644
index 00000000..411f52c5
--- /dev/null
+++ b/test/mocha.js
@@ -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);
+ });
+ }
+ });
+};
\ No newline at end of file
diff --git a/test/mocha/arguments.js b/test/mocha/arguments.js
new file mode 100644
index 00000000..089826fc
--- /dev/null
+++ b/test/mocha/arguments.js
@@ -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
+ );
+ });
+});
\ No newline at end of file
diff --git a/test/mocha/comment-filter.js b/test/mocha/comment-filter.js
new file mode 100644
index 00000000..ea2ec2eb
--- /dev/null
+++ b/test/mocha/comment-filter.js
@@ -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\ntest7\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\ntest7\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\ntest7\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");
+ });
+});
diff --git a/test/mocha/getter-setter.js b/test/mocha/getter-setter.js
new file mode 100644
index 00000000..641a2026
--- /dev/null
+++ b/test/mocha/getter-setter.js
@@ -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));
+ }
+ });
+
+});
diff --git a/test/mocha/string-literal.js b/test/mocha/string-literal.js
new file mode 100644
index 00000000..84aaad7e
--- /dev/null
+++ b/test/mocha/string-literal.js
@@ -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";');
+ });
+});
\ No newline at end of file
diff --git a/test/run-tests.js b/test/run-tests.js
index c1530109..9a31d148 100755
--- a/test/run-tests.js
+++ b/test/run-tests.js
@@ -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();
@@ -105,6 +108,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);
diff --git a/tools/exports.js b/tools/exports.js
index 5007e03b..110b5c4e 100644
--- a/tools/exports.js
+++ b/tools/exports.js
@@ -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;
diff --git a/tools/node.js b/tools/node.js
index f6048661..fa8c19dc 100644
--- a/tools/node.js
+++ b/tools/node.js
@@ -32,15 +32,18 @@ UglifyJS.AST_Node.warn_function = function(txt) {
exports.minify = function(files, options) {
options = UglifyJS.defaults(options, {
- spidermonkey : false,
- outSourceMap : null,
- sourceRoot : null,
- inSourceMap : null,
- fromString : false,
- warnings : false,
- mangle : {},
- output : null,
- compress : {}
+ spidermonkey : false,
+ outSourceMap : null,
+ sourceRoot : null,
+ inSourceMap : null,
+ fromString : false,
+ warnings : false,
+ mangle : {},
+ mangleProperties : false,
+ nameCache : null,
+ output : null,
+ 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") {