aboutsummaryrefslogtreecommitdiff
path: root/node_modules/uglify-js/lib
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/uglify-js/lib')
-rw-r--r--node_modules/uglify-js/lib/ast.js160
-rw-r--r--node_modules/uglify-js/lib/compress.js2552
-rw-r--r--node_modules/uglify-js/lib/minify.js220
-rw-r--r--node_modules/uglify-js/lib/mozilla-ast.js16
-rw-r--r--node_modules/uglify-js/lib/output.js267
-rw-r--r--node_modules/uglify-js/lib/parse.js152
-rw-r--r--node_modules/uglify-js/lib/propmangle.js121
-rw-r--r--node_modules/uglify-js/lib/scope.js289
-rw-r--r--node_modules/uglify-js/lib/transform.js7
-rw-r--r--node_modules/uglify-js/lib/utils.js9
10 files changed, 2081 insertions, 1712 deletions
diff --git a/node_modules/uglify-js/lib/ast.js b/node_modules/uglify-js/lib/ast.js
index 028772f3f..0918574d1 100644
--- a/node_modules/uglify-js/lib/ast.js
+++ b/node_modules/uglify-js/lib/ast.js
@@ -182,21 +182,13 @@ var AST_BlockStatement = DEFNODE("BlockStatement", null, {
}, AST_Block);
var AST_EmptyStatement = DEFNODE("EmptyStatement", null, {
- $documentation: "The empty statement (empty block or simply a semicolon)",
- _walk: function(visitor) {
- return visitor._visit(this);
- }
+ $documentation: "The empty statement (empty block or simply a semicolon)"
}, AST_Statement);
var AST_StatementWithBody = DEFNODE("StatementWithBody", "body", {
$documentation: "Base class for all statements that contain one nested body: `For`, `ForIn`, `Do`, `While`, `With`",
$propdoc: {
body: "[AST_Statement] the body; this should always be present, even if it's an AST_EmptyStatement"
- },
- _walk: function(visitor) {
- return visitor._visit(this, function(){
- this.body._walk(visitor);
- });
}
}, AST_Statement);
@@ -326,62 +318,13 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", {
$propdoc: {
globals: "[Object/S] a map of name -> SymbolDef for all undeclared names",
},
- wrap_enclose: function(arg_parameter_pairs) {
- var self = this;
- var args = [];
- var parameters = [];
-
- arg_parameter_pairs.forEach(function(pair) {
- var splitAt = pair.lastIndexOf(":");
-
- args.push(pair.substr(0, splitAt));
- parameters.push(pair.substr(splitAt + 1));
- });
-
- var wrapped_tl = "(function(" + parameters.join(",") + "){ '$ORIG'; })(" + args.join(",") + ")";
+ wrap_commonjs: function(name) {
+ var body = this.body;
+ var wrapped_tl = "(function(exports){'$ORIG';})(typeof " + name + "=='undefined'?(" + name + "={}):" + name + ");";
wrapped_tl = parse(wrapped_tl);
wrapped_tl = wrapped_tl.transform(new TreeTransformer(function before(node){
if (node instanceof AST_Directive && node.value == "$ORIG") {
- return MAP.splice(self.body);
- }
- }));
- return wrapped_tl;
- },
- wrap_commonjs: function(name, export_all) {
- var self = this;
- var to_export = [];
- if (export_all) {
- self.figure_out_scope();
- self.walk(new TreeWalker(function(node){
- if (node instanceof AST_SymbolDeclaration && node.definition().global) {
- if (!find_if(function(n){ return n.name == node.name }, to_export))
- to_export.push(node);
- }
- }));
- }
- var wrapped_tl = "(function(exports, global){ '$ORIG'; '$EXPORTS'; global['" + name + "'] = exports; }({}, (function(){return this}())))";
- wrapped_tl = parse(wrapped_tl);
- wrapped_tl = wrapped_tl.transform(new TreeTransformer(function before(node){
- if (node instanceof AST_Directive) {
- switch (node.value) {
- case "$ORIG":
- return MAP.splice(self.body);
- case "$EXPORTS":
- var body = [];
- to_export.forEach(function(sym){
- body.push(new AST_SimpleStatement({
- body: new AST_Assign({
- left: new AST_Sub({
- expression: new AST_SymbolRef({ name: "exports" }),
- property: new AST_String({ value: sym.name }),
- }),
- operator: "=",
- right: new AST_SymbolRef(sym),
- }),
- }));
- });
- return MAP.splice(body);
- }
+ return MAP.splice(body);
}
}));
return wrapped_tl;
@@ -552,10 +495,10 @@ var AST_Finally = DEFNODE("Finally", null, {
$documentation: "A `finally` node; only makes sense as part of a `try` statement"
}, AST_Block);
-/* -----[ VAR/CONST ]----- */
+/* -----[ VAR ]----- */
var AST_Definitions = DEFNODE("Definitions", "definitions", {
- $documentation: "Base class for `var` or `const` nodes (variable declarations/initializations)",
+ $documentation: "Base class for `var` nodes (variable declarations/initializations)",
$propdoc: {
definitions: "[AST_VarDef*] array of variable definitions"
},
@@ -573,14 +516,10 @@ var AST_Var = DEFNODE("Var", null, {
$documentation: "A `var` statement"
}, AST_Definitions);
-var AST_Const = DEFNODE("Const", null, {
- $documentation: "A `const` statement"
-}, AST_Definitions);
-
var AST_VarDef = DEFNODE("VarDef", "name value", {
$documentation: "A variable declaration; only appears in a AST_Definitions node",
$propdoc: {
- name: "[AST_SymbolVar|AST_SymbolConst] name of the variable",
+ name: "[AST_SymbolVar] name of the variable",
value: "[AST_Node?] initializer, or null of there's no initializer"
},
_walk: function(visitor) {
@@ -601,11 +540,11 @@ var AST_Call = DEFNODE("Call", "expression args", {
},
_walk: function(visitor) {
return visitor._visit(this, function(){
- this.expression._walk(visitor);
var args = this.args;
for (var i = 0, len = args.length; i < len; i++) {
args[i]._walk(visitor);
}
+ this.expression._walk(visitor);
});
}
});
@@ -614,68 +553,16 @@ var AST_New = DEFNODE("New", null, {
$documentation: "An object instantiation. Derives from a function call since it has exactly the same properties"
}, AST_Call);
-var AST_Seq = DEFNODE("Seq", "car cdr", {
- $documentation: "A sequence expression (two comma-separated expressions)",
+var AST_Sequence = DEFNODE("Sequence", "expressions", {
+ $documentation: "A sequence expression (comma-separated expressions)",
$propdoc: {
- car: "[AST_Node] first element in sequence",
- cdr: "[AST_Node] second element in sequence"
- },
- $cons: function(x, y) {
- var seq = new AST_Seq(x);
- seq.car = x;
- seq.cdr = y;
- return seq;
- },
- $from_array: function(array) {
- if (array.length == 0) return null;
- if (array.length == 1) return array[0].clone();
- var list = null;
- for (var i = array.length; --i >= 0;) {
- list = AST_Seq.cons(array[i], list);
- }
- var p = list;
- while (p) {
- if (p.cdr && !p.cdr.cdr) {
- p.cdr = p.cdr.car;
- break;
- }
- p = p.cdr;
- }
- return list;
- },
- to_array: function() {
- var p = this, a = [];
- while (p) {
- a.push(p.car);
- if (p.cdr && !(p.cdr instanceof AST_Seq)) {
- a.push(p.cdr);
- break;
- }
- p = p.cdr;
- }
- return a;
- },
- add: function(node) {
- var p = this;
- while (p) {
- if (!(p.cdr instanceof AST_Seq)) {
- var cell = AST_Seq.cons(p.cdr, node);
- return p.cdr = cell;
- }
- p = p.cdr;
- }
- },
- len: function() {
- if (this.cdr instanceof AST_Seq) {
- return this.cdr.len() + 1;
- } else {
- return 2;
- }
+ expressions: "[AST_Node*] array of expressions (at least two)"
},
_walk: function(visitor) {
return visitor._visit(this, function(){
- this.car._walk(visitor);
- if (this.cdr) this.cdr._walk(visitor);
+ this.expressions.forEach(function(node) {
+ node._walk(visitor);
+ });
});
}
});
@@ -728,7 +615,7 @@ var AST_UnaryPostfix = DEFNODE("UnaryPostfix", null, {
$documentation: "Unary postfix expression, i.e. `i++`"
}, AST_Unary);
-var AST_Binary = DEFNODE("Binary", "left operator right", {
+var AST_Binary = DEFNODE("Binary", "operator left right", {
$documentation: "Binary expression, i.e. `a + b`",
$propdoc: {
left: "[AST_Node] left-hand side expression",
@@ -837,17 +724,13 @@ var AST_SymbolAccessor = DEFNODE("SymbolAccessor", null, {
}, AST_Symbol);
var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", "init", {
- $documentation: "A declaration symbol (symbol in var/const, function name or argument, symbol in catch)",
+ $documentation: "A declaration symbol (symbol in var, function name or argument, symbol in catch)",
}, AST_Symbol);
var AST_SymbolVar = DEFNODE("SymbolVar", null, {
$documentation: "Symbol defining a variable",
}, AST_SymbolDeclaration);
-var AST_SymbolConst = DEFNODE("SymbolConst", null, {
- $documentation: "A constant declaration"
-}, AST_SymbolDeclaration);
-
var AST_SymbolFunarg = DEFNODE("SymbolFunarg", null, {
$documentation: "Symbol naming a function argument",
}, AST_SymbolVar);
@@ -976,13 +859,13 @@ TreeWalker.prototype = {
if (!ret && descend) {
descend.call(node);
}
- this.pop(node);
+ this.pop();
return ret;
},
parent: function(n) {
return this.stack[this.stack.length - 2 - (n || 0)];
},
- push: function (node) {
+ push: function(node) {
if (node instanceof AST_Lambda) {
this.directives = Object.create(this.directives);
} else if (node instanceof AST_Directive && !this.directives[node.value]) {
@@ -990,9 +873,8 @@ TreeWalker.prototype = {
}
this.stack.push(node);
},
- pop: function(node) {
- this.stack.pop();
- if (node instanceof AST_Lambda) {
+ pop: function() {
+ if (this.stack.pop() instanceof AST_Lambda) {
this.directives = Object.getPrototypeOf(this.directives);
}
},
diff --git a/node_modules/uglify-js/lib/compress.js b/node_modules/uglify-js/lib/compress.js
index d8a491ebc..7a16ba86b 100644
--- a/node_modules/uglify-js/lib/compress.js
+++ b/node_modules/uglify-js/lib/compress.js
@@ -48,7 +48,6 @@ function Compressor(options, false_by_default) {
return new Compressor(options, false_by_default);
TreeTransformer.call(this, this.before, this.after);
this.options = defaults(options, {
- angular : false,
booleans : !false_by_default,
cascade : !false_by_default,
collapse_vars : !false_by_default,
@@ -62,7 +61,9 @@ function Compressor(options, false_by_default) {
global_defs : {},
hoist_funs : !false_by_default,
hoist_vars : false,
+ ie8 : false,
if_return : !false_by_default,
+ inline : !false_by_default,
join_vars : !false_by_default,
keep_fargs : true,
keep_fnames : false,
@@ -74,20 +75,29 @@ function Compressor(options, false_by_default) {
pure_getters : !false_by_default && "strict",
pure_funcs : null,
reduce_vars : !false_by_default,
- screw_ie8 : true,
sequences : !false_by_default,
side_effects : !false_by_default,
switches : !false_by_default,
top_retain : null,
toplevel : !!(options && options["top_retain"]),
+ typeofs : !false_by_default,
unsafe : false,
unsafe_comps : false,
+ unsafe_Func : false,
unsafe_math : false,
unsafe_proto : false,
unsafe_regexp : false,
unused : !false_by_default,
- warnings : true,
+ warnings : false,
}, true);
+ var global_defs = this.options["global_defs"];
+ if (typeof global_defs == "object") for (var key in global_defs) {
+ if (/^@/.test(key) && HOP(global_defs, key)) {
+ global_defs[key.slice(1)] = parse(global_defs[key], {
+ expression: true
+ });
+ }
+ }
var pure_funcs = this.options["pure_funcs"];
if (typeof pure_funcs == "function") {
this.pure_funcs = pure_funcs;
@@ -111,26 +121,50 @@ function Compressor(options, false_by_default) {
return top_retain.indexOf(def.name) >= 0;
};
}
+ var toplevel = this.options["toplevel"];
+ this.toplevel = typeof toplevel == "string" ? {
+ funcs: /funcs/.test(toplevel),
+ vars: /vars/.test(toplevel)
+ } : {
+ funcs: toplevel,
+ vars: toplevel
+ };
var sequences = this.options["sequences"];
- this.sequences_limit = sequences == 1 ? 200 : sequences | 0;
+ this.sequences_limit = sequences == 1 ? 800 : sequences | 0;
this.warnings_produced = {};
};
Compressor.prototype = new TreeTransformer;
merge(Compressor.prototype, {
option: function(key) { return this.options[key] },
+ exposed: function(def) {
+ if (def.global) for (var i = 0, len = def.orig.length; i < len; i++)
+ if (!this.toplevel[def.orig[i] instanceof AST_SymbolDefun ? "funcs" : "vars"])
+ return true;
+ return false;
+ },
compress: function(node) {
if (this.option("expression")) {
- node = node.process_expression(true);
+ node.process_expression(true);
}
var passes = +this.options.passes || 1;
- for (var pass = 0; pass < passes && pass < 3; ++pass) {
+ var last_count = 1 / 0;
+ for (var pass = 0; pass < passes; pass++) {
if (pass > 0 || this.option("reduce_vars"))
node.reset_opt_flags(this, true);
node = node.transform(this);
+ if (passes > 1) {
+ var count = 0;
+ node.walk(new TreeWalker(function() {
+ count++;
+ }));
+ this.info("pass " + pass + ": last_count: " + last_count + ", count: " + count);
+ if (count >= last_count) break;
+ last_count = count;
+ }
}
if (this.option("expression")) {
- node = node.process_expression(false);
+ node.process_expression(false);
}
return node;
},
@@ -202,7 +236,7 @@ merge(Compressor.prototype, {
return this.TYPE == node.TYPE && this.print_to_string() == node.print_to_string();
});
- AST_Node.DEFMETHOD("process_expression", function(insert, compressor) {
+ AST_Scope.DEFMETHOD("process_expression", function(insert, compressor) {
var self = this;
var tt = new TreeTransformer(function(node) {
if (insert && node instanceof AST_SimpleStatement) {
@@ -246,21 +280,20 @@ merge(Compressor.prototype, {
}
return node;
});
- return self.transform(tt);
+ self.transform(tt);
});
- AST_Node.DEFMETHOD("reset_opt_flags", function(compressor, rescan){
+ AST_Node.DEFMETHOD("reset_opt_flags", function(compressor, rescan) {
var reduce_vars = rescan && compressor.option("reduce_vars");
- var toplevel = compressor.option("toplevel");
var safe_ids = Object.create(null);
var suppressor = new TreeWalker(function(node) {
- if (node instanceof AST_Symbol) {
- var d = node.definition();
- if (node instanceof AST_SymbolRef) d.references.push(node);
- d.fixed = false;
- }
+ if (!(node instanceof AST_Symbol)) return;
+ var d = node.definition();
+ if (!d) return;
+ if (node instanceof AST_SymbolRef) d.references.push(node);
+ d.fixed = false;
});
- var tw = new TreeWalker(function(node, descend){
+ var tw = new TreeWalker(function(node, descend) {
node._squeezed = false;
node._optimized = false;
if (reduce_vars) {
@@ -269,8 +302,8 @@ merge(Compressor.prototype, {
if (node instanceof AST_SymbolRef) {
var d = node.definition();
d.references.push(node);
- if (d.fixed === undefined || !is_safe(d)
- || is_modified(node, 0, node.fixed_value() instanceof AST_Lambda)) {
+ if (d.fixed === undefined || !safe_to_read(d)
+ || is_modified(node, 0, is_immutable(node.fixed_value()))) {
d.fixed = false;
} else {
var parent = tw.parent();
@@ -287,7 +320,7 @@ merge(Compressor.prototype, {
}
if (node instanceof AST_VarDef) {
var d = node.name.definition();
- if (d.fixed == null) {
+ if (d.fixed === undefined || safe_to_assign(d, node.value)) {
if (node.value) {
d.fixed = function() {
return node.value;
@@ -303,9 +336,24 @@ merge(Compressor.prototype, {
d.fixed = false;
}
}
+ if (node instanceof AST_Assign
+ && node.operator == "="
+ && node.left instanceof AST_SymbolRef) {
+ var d = node.left.definition();
+ if (safe_to_assign(d, node.right)) {
+ d.references.push(node.left);
+ d.fixed = function() {
+ return node.right;
+ };
+ mark(d, false);
+ node.right.walk(tw);
+ mark(d, true);
+ return true;
+ }
+ }
if (node instanceof AST_Defun) {
var d = node.name.definition();
- if (!toplevel && d.global || is_safe(d)) {
+ if (compressor.exposed(d) || safe_to_read(d)) {
d.fixed = false;
} else {
d.fixed = node;
@@ -367,7 +415,7 @@ merge(Compressor.prototype, {
pop();
return true;
}
- if (node instanceof AST_If || node instanceof AST_DWLoop) {
+ if (node instanceof AST_If) {
node.condition.walk(tw);
push();
node.body.walk(tw);
@@ -379,6 +427,13 @@ merge(Compressor.prototype, {
}
return true;
}
+ if (node instanceof AST_DWLoop) {
+ push();
+ node.condition.walk(tw);
+ node.body.walk(tw);
+ pop();
+ return true;
+ }
if (node instanceof AST_LabeledStatement) {
push();
node.body.walk(tw);
@@ -387,11 +442,19 @@ merge(Compressor.prototype, {
}
if (node instanceof AST_For) {
if (node.init) node.init.walk(tw);
+ if (node.condition) {
+ push();
+ node.condition.walk(tw);
+ pop();
+ }
push();
- if (node.condition) node.condition.walk(tw);
node.body.walk(tw);
- if (node.step) node.step.walk(tw);
pop();
+ if (node.step) {
+ push();
+ node.step.walk(tw);
+ pop();
+ }
return true;
}
if (node instanceof AST_ForIn) {
@@ -428,7 +491,7 @@ merge(Compressor.prototype, {
safe_ids[def.id] = safe;
}
- function is_safe(def) {
+ function safe_to_read(def) {
if (safe_ids[def.id]) {
if (def.fixed == null) {
var orig = def.orig[0];
@@ -439,6 +502,17 @@ merge(Compressor.prototype, {
}
}
+ function safe_to_assign(def, value) {
+ if (!HOP(safe_ids, def.id)) return false;
+ if (!safe_to_read(def)) return false;
+ if (def.fixed === false) return false;
+ if (def.fixed != null && (!value || def.references.length > 0)) return false;
+ return !def.orig.some(function(sym) {
+ return sym instanceof AST_SymbolDefun
+ || sym instanceof AST_SymbolLambda;
+ });
+ }
+
function push() {
safe_ids = Object.create(safe_ids);
}
@@ -451,7 +525,7 @@ merge(Compressor.prototype, {
def.escaped = false;
if (def.scope.uses_eval) {
def.fixed = false;
- } else if (toplevel || !def.global || def.orig[0] instanceof AST_SymbolConst) {
+ } else if (!compressor.exposed(def)) {
def.fixed = undefined;
} else {
def.fixed = false;
@@ -460,13 +534,17 @@ merge(Compressor.prototype, {
def.should_replace = undefined;
}
- function is_modified(node, level, func) {
+ function is_immutable(value) {
+ return value && value.is_constant() || value instanceof AST_Lambda;
+ }
+
+ function is_modified(node, level, immutable) {
var parent = tw.parent(level);
if (is_lhs(node, parent)
- || !func && parent instanceof AST_Call && parent.expression === node) {
+ || !immutable && parent instanceof AST_Call && parent.expression === node) {
return true;
} else if (parent instanceof AST_PropAccess && parent.expression === node) {
- return !func && is_modified(parent, level + 1);
+ return !immutable && is_modified(parent, level + 1);
}
}
});
@@ -477,12 +555,25 @@ merge(Compressor.prototype, {
return fixed();
});
- function is_reference_const(ref) {
- if (!(ref instanceof AST_SymbolRef)) return false;
- var orig = ref.definition().orig;
- for (var i = orig.length; --i >= 0;) {
- if (orig[i] instanceof AST_SymbolConst) return true;
+ AST_SymbolRef.DEFMETHOD("is_immutable", function() {
+ var orig = this.definition().orig;
+ return orig.length == 1 && orig[0] instanceof AST_SymbolLambda;
+ });
+
+ function is_lhs_read_only(lhs) {
+ if (lhs instanceof AST_SymbolRef) return lhs.definition().orig[0] instanceof AST_SymbolLambda;
+ if (lhs instanceof AST_PropAccess) {
+ lhs = lhs.expression;
+ if (lhs instanceof AST_SymbolRef) {
+ if (lhs.is_immutable()) return false;
+ lhs = lhs.fixed_value();
+ }
+ if (!lhs) return true;
+ if (lhs instanceof AST_RegExp) return false;
+ if (lhs instanceof AST_Constant) return true;
+ return is_lhs_read_only(lhs);
}
+ return false;
}
function find_variable(compressor, name) {
@@ -506,6 +597,13 @@ merge(Compressor.prototype, {
return new ctor(props);
};
+ function make_sequence(orig, expressions) {
+ if (expressions.length == 1) return expressions[0];
+ return make_node(AST_Sequence, orig, {
+ expressions: expressions
+ });
+ }
+
function make_node_from_constant(val, orig) {
switch (typeof val) {
case "string":
@@ -548,16 +646,19 @@ merge(Compressor.prototype, {
if (parent instanceof AST_UnaryPrefix && parent.operator == "delete"
|| parent instanceof AST_Call && parent.expression === orig
&& (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 make_sequence(orig, [ make_node(AST_Number, orig, { value: 0 }), val ]);
}
return val;
}
+ function merge_sequence(array, node) {
+ if (node instanceof AST_Sequence) {
+ array.push.apply(array, node.expressions);
+ } else {
+ array.push(node);
+ }
+ }
+
function as_statement_array(thing) {
if (thing === null) return [];
if (thing instanceof AST_BlockStatement) return thing.body;
@@ -588,436 +689,425 @@ merge(Compressor.prototype, {
return false;
}
+ function is_undeclared_ref(node) {
+ return node instanceof AST_SymbolRef && node.definition().undeclared;
+ }
+
+ var global_names = makePredicate("Array Boolean console Error Function Math Number RegExp Object String");
+ AST_SymbolRef.DEFMETHOD("is_declared", function(compressor) {
+ return !this.definition().undeclared
+ || compressor.option("unsafe") && global_names(this.name);
+ });
+
function tighten_body(statements, compressor) {
var CHANGED, max_iter = 10;
do {
CHANGED = false;
- if (compressor.option("angular")) {
- statements = process_for_angular(statements);
- }
- statements = eliminate_spurious_blocks(statements);
+ eliminate_spurious_blocks(statements);
if (compressor.option("dead_code")) {
- statements = eliminate_dead_code(statements, compressor);
+ eliminate_dead_code(statements, compressor);
}
if (compressor.option("if_return")) {
- statements = handle_if_return(statements, compressor);
+ handle_if_return(statements, compressor);
}
if (compressor.sequences_limit > 0) {
- statements = sequencesize(statements, compressor);
+ sequencesize(statements, compressor);
}
if (compressor.option("join_vars")) {
- statements = join_consecutive_vars(statements, compressor);
+ join_consecutive_vars(statements, compressor);
}
if (compressor.option("collapse_vars")) {
- statements = collapse_single_use_vars(statements, compressor);
+ collapse(statements, compressor);
}
} while (CHANGED && max_iter-- > 0);
- 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;
- var toplevel = compressor.option("toplevel");
- 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" || (!toplevel && def.global)) {
- 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()) {
- var ctt = new TreeTransformer(function(node) {
- var parent = ctt.parent();
- if (parent instanceof AST_IterationStatement
- && (parent.condition === node || parent.init === node)) {
+ // Search from right to left for assignment-like expressions:
+ // - `var a = x;`
+ // - `a = x;`
+ // - `++a`
+ // For each candidate, scan from left to right for first usage, then try
+ // to fold assignment into the site for compression.
+ // Will not attempt to collapse assignments into or past code blocks
+ // which are not sequentially executed, e.g. loops and conditionals.
+ function collapse(statements, compressor) {
+ var scope = compressor.find_parent(AST_Scope);
+ if (scope.uses_eval || scope.uses_with) return statements;
+ var candidates = [];
+ var stat_index = statements.length;
+ while (--stat_index >= 0) {
+ // Treat parameters as collapsible in IIFE, i.e.
+ // function(a, b){ ... }(x());
+ // would be translated into equivalent assignments:
+ // var a = x(), b = undefined;
+ if (stat_index == 0 && compressor.option("unused")) extract_args();
+ // Find collapsible assignments
+ extract_candidates(statements[stat_index]);
+ while (candidates.length > 0) {
+ var candidate = candidates.pop();
+ var lhs = get_lhs(candidate);
+ if (!lhs || is_lhs_read_only(lhs)) continue;
+ // Locate symbols which may execute code outside of scanning range
+ var lvalues = get_lvalues(candidate);
+ if (lhs instanceof AST_SymbolRef) lvalues[lhs.name] = false;
+ var side_effects = value_has_side_effects(candidate);
+ var hit = candidate.name instanceof AST_SymbolFunarg;
+ var abort = false, replaced = false;
+ var tt = new TreeTransformer(function(node, descend) {
+ if (abort) return node;
+ // Skip nodes before `candidate` as quickly as possible
+ if (!hit) {
+ if (node === candidate) {
+ hit = true;
return node;
}
- if (node === ref)
- return replace_var(node, parent, true);
- });
- stat.transform(ctt);
- continue;
- }
-
- // Restrict var replacement to constants if side effects encountered.
- if (side_effects_encountered |= lvalues_encountered) continue;
-
- var value_has_side_effects = var_decl.value.has_side_effects(compressor);
- // Non-constant single use vars can only be replaced in same scope.
- if (ref.scope !== self) {
- side_effects_encountered |= value_has_side_effects;
- 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;
+ return;
}
- });
- 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)
- || (node instanceof AST_SymbolRef
- && value_has_side_effects
- && !are_references_in_scope(node.definition(), self))
- || (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;
+ // Stop immediately if these node types are encountered
+ var parent = tt.parent();
+ if (node instanceof AST_Assign && node.operator != "=" && lhs.equivalent_to(node.left)
+ || node instanceof AST_Call && lhs instanceof AST_PropAccess && lhs.equivalent_to(node.expression)
+ || node instanceof AST_Debugger
+ || node instanceof AST_IterationStatement && !(node instanceof AST_For)
+ || node instanceof AST_SymbolRef && !node.is_declared(compressor)
+ || node instanceof AST_Try
+ || node instanceof AST_With
+ || parent instanceof AST_For && node !== parent.init) {
+ abort = true;
+ return node;
+ }
+ // Replace variable with assignment when found
+ if (!(node instanceof AST_SymbolDeclaration)
+ && !is_lhs(node, parent)
+ && lhs.equivalent_to(node)) {
+ CHANGED = replaced = abort = true;
+ compressor.info("Collapsing {name} [{file}:{line},{col}]", {
+ name: node.print_to_string(),
+ file: node.start.file,
+ line: node.start.line,
+ col: node.start.col
+ });
+ if (candidate instanceof AST_UnaryPostfix) {
+ return make_node(AST_UnaryPrefix, candidate, candidate);
}
- function are_references_in_scope(def, scope) {
- if (def.orig.length === 1
- && def.orig[0] instanceof AST_SymbolDefun) return true;
- if (def.scope !== scope) return false;
- var refs = def.references;
- for (var i = 0, len = refs.length; i < len; i++) {
- if (refs[i].scope !== scope) return false;
+ if (candidate instanceof AST_VarDef) {
+ var def = candidate.name.definition();
+ if (def.references.length == 1 && !compressor.exposed(def)) {
+ return maintain_this_binding(parent, node, candidate.value);
}
- return true;
- }
- },
- 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;
+ return make_node(AST_Assign, candidate, {
+ operator: "=",
+ left: make_node(AST_SymbolRef, candidate.name, candidate.name),
+ right: candidate.value
+ });
}
+ candidate.write_only = false;
+ return candidate;
}
- );
- stat.transform(tt);
+ // These node types have child nodes that execute sequentially,
+ // but are otherwise not safe to scan into or beyond them.
+ var sym;
+ if (node instanceof AST_Call
+ || node instanceof AST_Exit
+ || node instanceof AST_PropAccess
+ || node instanceof AST_SymbolRef
+ && (lvalues[node.name]
+ || side_effects && !references_in_scope(node.definition()))
+ || (sym = lhs_or_def(node)) && get_symbol(sym).name in lvalues
+ || parent instanceof AST_Binary
+ && (parent.operator == "&&" || parent.operator == "||")
+ || parent instanceof AST_Case
+ || parent instanceof AST_Conditional
+ || parent instanceof AST_For
+ || parent instanceof AST_If) {
+ if (!(node instanceof AST_Scope)) descend(node, tt);
+ abort = true;
+ return node;
+ }
+ // Skip (non-executed) functions and (leading) default case in switch statements
+ if (node instanceof AST_Default || node instanceof AST_Scope) return node;
+ });
+ for (var i = stat_index; !abort && i < statements.length; i++) {
+ statements[i].transform(tt);
+ }
+ if (replaced && !remove_candidate(candidate)) statements.splice(stat_index, 1);
}
}
- // 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);
+ function extract_args() {
+ var iife, fn = compressor.self();
+ if (fn instanceof AST_Function
+ && !fn.name
+ && !fn.uses_arguments
+ && !fn.uses_eval
+ && (iife = compressor.parent()) instanceof AST_Call
+ && iife.expression === fn) {
+ var names = Object.create(null);
+ for (var i = fn.argnames.length; --i >= 0;) {
+ var sym = fn.argnames[i];
+ if (sym.name in names) continue;
+ names[sym.name] = true;
+ var arg = iife.args[i];
+ if (!arg) arg = make_node(AST_Undefined, sym);
+ else {
+ var tw = new TreeWalker(function(node) {
+ if (!arg) return true;
+ if (node instanceof AST_SymbolRef && fn.variables.has(node.name)) {
+ var s = node.definition().scope;
+ if (s !== scope) while (s = s.parent_scope) {
+ if (s === scope) return true;
+ }
+ arg = null;
+ }
+ if (node instanceof AST_This && !tw.find_parent(AST_Scope)) {
+ arg = null;
+ return true;
+ }
+ });
+ arg.walk(tw);
+ }
+ if (arg) candidates.unshift(make_node(AST_VarDef, sym, {
+ name: sym,
+ value: arg
+ }));
+ }
+ }
}
- return statements;
-
- function is_lvalue(node, parent) {
- return node instanceof AST_SymbolRef && is_lhs(node, parent);
+ function extract_candidates(expr) {
+ if (expr instanceof AST_Assign && !expr.left.has_side_effects(compressor)
+ || expr instanceof AST_Unary && (expr.operator == "++" || expr.operator == "--")) {
+ candidates.push(expr);
+ } else if (expr instanceof AST_Sequence) {
+ expr.expressions.forEach(extract_candidates);
+ } else if (expr instanceof AST_Definitions) {
+ expr.definitions.forEach(function(var_def) {
+ if (var_def.value) candidates.push(var_def);
+ });
+ } else if (expr instanceof AST_SimpleStatement) {
+ extract_candidates(expr.body);
+ } else if (expr instanceof AST_For && expr.init) {
+ extract_candidates(expr.init);
+ }
}
- 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;
+ function get_lhs(expr) {
+ if (expr instanceof AST_VarDef) {
+ var def = expr.name.definition();
+ if (def.orig.length > 1 && !(expr.name instanceof AST_SymbolFunarg)
+ || def.references.length == 1 && !compressor.exposed(def)) {
+ return make_node(AST_SymbolRef, expr.name, expr.name);
+ }
+ } else {
+ return expr[expr instanceof AST_Assign ? "left" : "expression"];
}
- // Further optimize statement after substitution.
- stat.reset_opt_flags(compressor);
-
- compressor.info("Collapsing " + (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);
+ function get_symbol(node) {
+ while (node instanceof AST_PropAccess) node = node.expression;
+ return node;
}
- function make_arguments_names_list(func) {
- return func.argnames.map(function(sym){
- return make_node(AST_String, sym, { value: sym.name });
+
+ function get_lvalues(expr) {
+ var lvalues = Object.create(null);
+ if (expr instanceof AST_Unary) return lvalues;
+ var scope;
+ var tw = new TreeWalker(function(node, descend) {
+ if (node instanceof AST_Scope) {
+ var save_scope = scope;
+ descend();
+ scope = save_scope;
+ return true;
+ }
+ if (node instanceof AST_SymbolRef || node instanceof AST_PropAccess) {
+ var sym = get_symbol(node);
+ if (sym instanceof AST_SymbolRef) {
+ lvalues[sym.name] = lvalues[sym.name] || is_lhs(node, tw.parent());
+ }
+ }
});
+ expr[expr instanceof AST_Assign ? "right" : "value"].walk(tw);
+ return lvalues;
}
- function make_array(orig, elements) {
- return make_node(AST_Array, orig, { elements: elements });
- }
- function make_injector(func, name) {
- return make_node(AST_SimpleStatement, func, {
- body: make_node(AST_Assign, func, {
- operator: "=",
- left: make_node(AST_Dot, name, {
- expression: make_node(AST_SymbolRef, name, name),
- property: "$inject"
- }),
- right: make_array(func, make_arguments_names_list(func))
- })
- });
+
+ function lhs_or_def(node) {
+ if (node instanceof AST_VarDef) return node.value && node.name;
+ return is_lhs(node.left, node);
}
- function check_expression(body) {
- if (body && body.args) {
- // if this is a function call check all of arguments passed
- body.args.forEach(function(argument, index, array) {
- var comments = argument.start.comments_before;
- // if the argument is function preceded by @ngInject
- if (argument instanceof AST_Lambda && comments.length && has_inject(comments[0])) {
- // replace the function with an array of names of its parameters and function at the end
- array[index] = make_array(argument, make_arguments_names_list(argument).concat(argument));
- }
+
+ function remove_candidate(expr) {
+ if (expr.name instanceof AST_SymbolFunarg) {
+ var index = compressor.self().argnames.indexOf(expr.name);
+ var args = compressor.parent().args;
+ if (args[index]) args[index] = make_node(AST_Number, args[index], {
+ value: 0
});
- // if this is chained call check previous one recursively
- if (body.expression && body.expression.expression) {
- check_expression(body.expression.expression);
- }
+ return true;
}
- }
- return statements.reduce(function(a, stat){
- a.push(stat);
-
- if (stat.body && stat.body.args) {
- check_expression(stat.body);
- } else {
- var token = stat.start;
- var comments = token.comments_before;
- if (comments && comments.length > 0) {
- var last = comments.pop();
- if (has_inject(last)) {
- // case 1: defun
- if (stat instanceof AST_Defun) {
- a.push(make_injector(stat, stat.name));
- }
- else if (stat instanceof AST_Definitions) {
- stat.definitions.forEach(function(def) {
- if (def.value && def.value instanceof AST_Lambda) {
- a.push(make_injector(def.value, def.name));
- }
- });
- }
- else {
- compressor.warn("Unknown statement marked with @ngInject [{file}:{line},{col}]", token);
- }
+ var found = false;
+ return statements[stat_index].transform(new TreeTransformer(function(node, descend, in_list) {
+ if (found) return node;
+ if (node === expr) {
+ found = true;
+ if (node instanceof AST_VarDef) {
+ remove(node.name.definition().orig, node.name);
}
+ return in_list ? MAP.skip : null;
}
- }
+ }, function(node) {
+ if (node instanceof AST_Sequence) switch (node.expressions.length) {
+ case 0: return null;
+ case 1: return node.expressions[0];
+ }
+ if (node instanceof AST_Definitions && node.definitions.length == 0
+ || node instanceof AST_SimpleStatement && !node.body) {
+ return null;
+ }
+ }));
+ }
+
+ function value_has_side_effects(expr) {
+ if (expr instanceof AST_Unary) return false;
+ return expr[expr instanceof AST_Assign ? "right" : "value"].has_side_effects(compressor);
+ }
- return a;
- }, []);
+ function references_in_scope(def) {
+ if (def.orig.length == 1 && def.orig[0] instanceof AST_SymbolDefun) return true;
+ if (def.scope !== scope) return false;
+ return def.references.every(function(ref) {
+ return ref.scope === scope;
+ });
+ }
}
function eliminate_spurious_blocks(statements) {
var seen_dirs = [];
- return statements.reduce(function(a, stat){
+ for (var i = 0; i < statements.length;) {
+ var stat = statements[i];
if (stat instanceof AST_BlockStatement) {
CHANGED = true;
- a.push.apply(a, eliminate_spurious_blocks(stat.body));
+ eliminate_spurious_blocks(stat.body);
+ [].splice.apply(statements, [i, 1].concat(stat.body));
+ i += stat.body.length;
} else if (stat instanceof AST_EmptyStatement) {
CHANGED = true;
+ statements.splice(i, 1);
} else if (stat instanceof AST_Directive) {
if (seen_dirs.indexOf(stat.value) < 0) {
- a.push(stat);
+ i++;
seen_dirs.push(stat.value);
} else {
CHANGED = true;
+ statements.splice(i, 1);
}
- } else {
- a.push(stat);
- }
- return a;
- }, []);
- };
+ } else i++;
+ }
+ }
function handle_if_return(statements, compressor) {
var self = compressor.self();
var multiple_if_returns = has_multiple_if_returns(statements);
var in_lambda = self instanceof AST_Lambda;
- var ret = []; // Optimized statements, build from tail to front
- loop: for (var i = statements.length; --i >= 0;) {
+ for (var i = statements.length; --i >= 0;) {
var stat = statements[i];
- switch (true) {
- case (in_lambda && stat instanceof AST_Return && !stat.value && ret.length == 0):
- CHANGED = true;
- // note, ret.length is probably always zero
- // because we drop unreachable code before this
- // step. nevertheless, it's good to check.
- continue loop;
- case stat instanceof AST_If:
- if (stat.body instanceof AST_Return) {
- //---
- // pretty silly case, but:
- // if (foo()) return; return; ==> foo(); return;
- if (((in_lambda && ret.length == 0)
- || (ret[0] instanceof AST_Return && !ret[0].value))
- && !stat.body.value && !stat.alternative) {
- CHANGED = true;
- var cond = make_node(AST_SimpleStatement, stat.condition, {
- body: stat.condition
- });
- ret.unshift(cond);
- continue loop;
- }
- //---
- // if (foo()) return x; return y; ==> return foo() ? x : y;
- if (ret[0] instanceof AST_Return && stat.body.value && ret[0].value && !stat.alternative) {
- CHANGED = true;
- stat = stat.clone();
- stat.alternative = ret[0];
- ret[0] = stat.transform(compressor);
- continue loop;
- }
- //---
- // if (foo()) return x; [ return ; ] ==> return foo() ? x : undefined;
- if (multiple_if_returns && (ret.length == 0 || ret[0] instanceof AST_Return)
- && stat.body.value && !stat.alternative && in_lambda) {
- CHANGED = true;
- stat = stat.clone();
- stat.alternative = ret[0] || make_node(AST_Return, stat, {
- value: null
- });
- ret[0] = stat.transform(compressor);
- continue loop;
- }
- //---
- // if (foo()) return; [ else x... ]; y... ==> if (!foo()) { x...; y... }
- if (!stat.body.value && in_lambda) {
- CHANGED = true;
- stat = stat.clone();
- stat.condition = stat.condition.negate(compressor);
- var body = as_statement_array(stat.alternative).concat(ret);
- var funs = extract_functions_from_statement_array(body);
- stat.body = make_node(AST_BlockStatement, stat, {
- body: body
- });
- stat.alternative = null;
- ret = funs.concat([ stat.transform(compressor) ]);
- continue loop;
- }
+ var next = statements[i + 1];
- //---
- // if (a) return b; if (c) return d; e; ==> return a ? b : c ? d : void e;
- //
- // if sequences is not enabled, this can lead to an endless loop (issue #866).
- // however, with sequences on this helps producing slightly better output for
- // the example code.
- if (compressor.option("sequences")
- && i > 0 && statements[i - 1] instanceof AST_If && statements[i - 1].body instanceof AST_Return
- && ret.length == 1 && in_lambda && ret[0] instanceof AST_SimpleStatement
- && !stat.alternative) {
- CHANGED = true;
- ret.push(make_node(AST_Return, ret[0], {
- value: null
- }).transform(compressor));
- ret.unshift(stat);
- continue loop;
- }
- }
+ if (in_lambda && stat instanceof AST_Return && !stat.value && !next) {
+ CHANGED = true;
+ statements.length--;
+ continue;
+ }
+ if (stat instanceof AST_If) {
var ab = aborts(stat.body);
- var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab) : null;
- if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda)
- || (ab instanceof AST_Continue && self === loop_body(lct))
- || (ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct))) {
+ if (can_merge_flow(ab)) {
if (ab.label) {
remove(ab.label.thedef.references, ab);
}
CHANGED = true;
- var body = as_statement_array(stat.body).slice(0, -1);
stat = stat.clone();
stat.condition = stat.condition.negate(compressor);
+ var body = as_statement_array_with_return(stat.body, ab);
stat.body = make_node(AST_BlockStatement, stat, {
- body: as_statement_array(stat.alternative).concat(ret)
+ body: as_statement_array(stat.alternative).concat(extract_functions())
});
stat.alternative = make_node(AST_BlockStatement, stat, {
body: body
});
- ret = [ stat.transform(compressor) ];
- continue loop;
+ statements[i] = stat.transform(compressor);
+ continue;
}
var ab = aborts(stat.alternative);
- var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab) : null;
- if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda)
- || (ab instanceof AST_Continue && self === loop_body(lct))
- || (ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct))) {
+ if (can_merge_flow(ab)) {
if (ab.label) {
remove(ab.label.thedef.references, ab);
}
CHANGED = true;
stat = stat.clone();
stat.body = make_node(AST_BlockStatement, stat.body, {
- body: as_statement_array(stat.body).concat(ret)
+ body: as_statement_array(stat.body).concat(extract_functions())
});
+ var body = as_statement_array_with_return(stat.alternative, ab);
stat.alternative = make_node(AST_BlockStatement, stat.alternative, {
- body: as_statement_array(stat.alternative).slice(0, -1)
+ body: body
});
- ret = [ stat.transform(compressor) ];
- continue loop;
+ statements[i] = stat.transform(compressor);
+ continue;
}
+ }
- ret.unshift(stat);
- break;
- default:
- ret.unshift(stat);
- break;
+ if (stat instanceof AST_If && stat.body instanceof AST_Return) {
+ var value = stat.body.value;
+ //---
+ // pretty silly case, but:
+ // if (foo()) return; return; ==> foo(); return;
+ if (!value && !stat.alternative
+ && (in_lambda && !next || next instanceof AST_Return && !next.value)) {
+ CHANGED = true;
+ statements[i] = make_node(AST_SimpleStatement, stat.condition, {
+ body: stat.condition
+ });
+ continue;
+ }
+ //---
+ // if (foo()) return x; return y; ==> return foo() ? x : y;
+ if (value && !stat.alternative && next instanceof AST_Return && next.value) {
+ CHANGED = true;
+ stat = stat.clone();
+ stat.alternative = next;
+ statements.splice(i, 2, stat.transform(compressor));
+ continue;
+ }
+ //---
+ // if (foo()) return x; [ return ; ] ==> return foo() ? x : undefined;
+ if (multiple_if_returns && in_lambda && value && !stat.alternative
+ && (!next || next instanceof AST_Return)) {
+ CHANGED = true;
+ stat = stat.clone();
+ stat.alternative = next || make_node(AST_Return, stat, {
+ value: null
+ });
+ statements.splice(i, next ? 2 : 1, stat.transform(compressor));
+ continue;
+ }
+ //---
+ // if (a) return b; if (c) return d; e; ==> return a ? b : c ? d : void e;
+ //
+ // if sequences is not enabled, this can lead to an endless loop (issue #866).
+ // however, with sequences on this helps producing slightly better output for
+ // the example code.
+ var prev = statements[i - 1];
+ if (compressor.option("sequences") && in_lambda && !stat.alternative
+ && prev instanceof AST_If && prev.body instanceof AST_Return
+ && i + 2 == statements.length && next instanceof AST_SimpleStatement) {
+ CHANGED = true;
+ statements.push(make_node(AST_Return, next, {
+ value: null
+ }).transform(compressor));
+ continue;
+ }
}
}
- return ret;
function has_multiple_if_returns(statements) {
var n = 0;
@@ -1029,108 +1119,133 @@ merge(Compressor.prototype, {
}
return false;
}
- };
+
+ function is_return_void(value) {
+ return !value || value instanceof AST_UnaryPrefix && value.operator == "void";
+ }
+
+ function can_merge_flow(ab) {
+ if (!ab) return false;
+ var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab) : null;
+ return ab instanceof AST_Return && in_lambda && is_return_void(ab.value)
+ || ab instanceof AST_Continue && self === loop_body(lct)
+ || ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct;
+ }
+
+ function extract_functions() {
+ var tail = statements.slice(i + 1);
+ statements.length = i + 1;
+ return tail.filter(function(stat) {
+ if (stat instanceof AST_Defun) {
+ statements.push(stat);
+ return false;
+ }
+ return true;
+ });
+ }
+
+ function as_statement_array_with_return(node, ab) {
+ var body = as_statement_array(node).slice(0, -1);
+ if (ab.value) {
+ body.push(make_node(AST_SimpleStatement, ab.value, {
+ body: ab.value.expression
+ }));
+ }
+ return body;
+ }
+ }
function eliminate_dead_code(statements, compressor) {
- var has_quit = false;
- var orig = statements.length;
+ var has_quit;
var self = compressor.self();
- statements = statements.reduce(function(a, stat){
- if (has_quit) {
- extract_declarations_from_unreachable_code(compressor, stat, a);
- } else {
- if (stat instanceof AST_LoopControl) {
- var lct = compressor.loopcontrol_target(stat);
- if ((stat instanceof AST_Break
- && !(lct instanceof AST_IterationStatement)
- && loop_body(lct) === self) || (stat instanceof AST_Continue
- && loop_body(lct) === self)) {
- if (stat.label) {
- remove(stat.label.thedef.references, stat);
- }
- } else {
- a.push(stat);
+ for (var i = 0, n = 0, len = statements.length; i < len; i++) {
+ var stat = statements[i];
+ if (stat instanceof AST_LoopControl) {
+ var lct = compressor.loopcontrol_target(stat);
+ if (stat instanceof AST_Break
+ && !(lct instanceof AST_IterationStatement)
+ && loop_body(lct) === self
+ || stat instanceof AST_Continue
+ && loop_body(lct) === self) {
+ if (stat.label) {
+ remove(stat.label.thedef.references, stat);
}
} else {
- a.push(stat);
+ statements[n++] = stat;
}
- if (aborts(stat)) has_quit = true;
+ } else {
+ statements[n++] = stat;
}
- return a;
- }, []);
- CHANGED = statements.length != orig;
- return statements;
- };
+ if (aborts(stat)) {
+ has_quit = statements.slice(i + 1);
+ break;
+ }
+ }
+ statements.length = n;
+ CHANGED = n != len;
+ if (has_quit) has_quit.forEach(function(stat) {
+ extract_declarations_from_unreachable_code(compressor, stat, statements);
+ });
+ }
function sequencesize(statements, compressor) {
- if (statements.length < 2) return statements;
- var seq = [], ret = [];
+ if (statements.length < 2) return;
+ var seq = [], n = 0;
function push_seq() {
- seq = AST_Seq.from_array(seq);
- if (seq) ret.push(make_node(AST_SimpleStatement, seq, {
- body: seq
- }));
+ if (!seq.length) return;
+ var body = make_sequence(seq[0], seq);
+ statements[n++] = make_node(AST_SimpleStatement, body, { body: body });
seq = [];
- };
- statements.forEach(function(stat){
+ }
+ for (var i = 0, len = statements.length; i < len; i++) {
+ var stat = statements[i];
if (stat instanceof AST_SimpleStatement) {
- if (seqLength(seq) >= compressor.sequences_limit) push_seq();
+ if (seq.length >= compressor.sequences_limit) push_seq();
var body = stat.body;
if (seq.length > 0) body = body.drop_side_effect_free(compressor);
- if (body) seq.push(body);
+ if (body) merge_sequence(seq, body);
} else {
push_seq();
- ret.push(stat);
- }
- });
- push_seq();
- ret = sequencesize_2(ret, compressor);
- CHANGED = ret.length != statements.length;
- 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++;
+ statements[n++] = stat;
}
}
- return len;
- };
+ push_seq();
+ statements.length = n;
+ sequencesize_2(statements, compressor);
+ CHANGED = statements.length != len;
+ }
function sequencesize_2(statements, compressor) {
function cons_seq(right) {
- ret.pop();
+ n--;
var left = prev.body;
- if (left instanceof AST_Seq) {
- left.add(right);
- } else {
- left = AST_Seq.cons(left, right);
+ if (!(left instanceof AST_Sequence)) {
+ left = make_node(AST_Sequence, left, {
+ expressions: [ left ]
+ });
}
+ merge_sequence(left.expressions, right);
return left.transform(compressor);
};
- var ret = [], prev = null;
- statements.forEach(function(stat){
+ var n = 0, prev;
+ for (var i = 0, len = statements.length; i < len; i++) {
+ var stat = statements[i];
if (prev) {
- if (stat instanceof AST_For) {
- var opera = {};
- try {
- prev.body.walk(new TreeWalker(function(node){
- if (node instanceof AST_Binary && node.operator == "in")
- throw opera;
- }));
- if (stat.init && !(stat.init instanceof AST_Definitions)) {
- stat.init = cons_seq(stat.init);
+ if (stat instanceof AST_For && !(stat.init instanceof AST_Definitions)) {
+ var abort = false;
+ prev.body.walk(new TreeWalker(function(node) {
+ if (abort || node instanceof AST_Scope) return true;
+ if (node instanceof AST_Binary && node.operator == "in") {
+ abort = true;
+ return true;
}
- else if (!stat.init) {
+ }));
+ if (!abort) {
+ if (stat.init) stat.init = cons_seq(stat.init);
+ else {
stat.init = prev.body.drop_side_effect_free(compressor);
- ret.pop();
+ n--;
}
- } catch(ex) {
- if (ex !== opera) throw ex;
}
}
else if (stat instanceof AST_If) {
@@ -1149,15 +1264,16 @@ merge(Compressor.prototype, {
stat.expression = cons_seq(stat.expression);
}
}
- ret.push(stat);
+ statements[n++] = stat;
prev = stat instanceof AST_SimpleStatement ? stat : null;
- });
- return ret;
- };
+ }
+ statements.length = n;
+ }
function join_consecutive_vars(statements, compressor) {
- var prev = null;
- return statements.reduce(function(a, stat){
+ for (var i = 0, j = -1, len = statements.length; i < len; i++) {
+ var stat = statements[i];
+ var prev = statements[j];
if (stat instanceof AST_Definitions && prev && prev.TYPE == stat.TYPE) {
prev.definitions = prev.definitions.concat(stat.definitions);
CHANGED = true;
@@ -1166,35 +1282,19 @@ merge(Compressor.prototype, {
&& prev instanceof AST_Var
&& (!stat.init || stat.init.TYPE == prev.TYPE)) {
CHANGED = true;
- a.pop();
if (stat.init) {
stat.init.definitions = prev.definitions.concat(stat.init.definitions);
} else {
stat.init = prev;
}
- a.push(stat);
- prev = stat;
+ statements[j] = stat;
}
else {
- prev = stat;
- a.push(stat);
+ statements[++j] = stat;
}
- return a;
- }, []);
- };
-
- };
-
- function extract_functions_from_statement_array(statements) {
- var funs = [];
- for (var i = statements.length - 1; i >= 0; --i) {
- var stat = statements[i];
- if (stat instanceof AST_Defun) {
- statements.splice(i, 1);
- funs.unshift(stat);
}
- }
- return funs;
+ statements.length = j + 1;
+ };
}
function extract_declarations_from_unreachable_code(compressor, stat, target) {
@@ -1208,7 +1308,7 @@ merge(Compressor.prototype, {
target.push(node);
return true;
}
- if (node instanceof AST_Defun) {
+ if (node instanceof AST_Defun && (node === stat || !compressor.has_directive("use strict"))) {
target.push(node);
return true;
}
@@ -1230,12 +1330,12 @@ merge(Compressor.prototype, {
// returns true if this node may be null, undefined or contain `AST_Accessor`
(function(def) {
AST_Node.DEFMETHOD("may_throw_on_access", function(compressor) {
- var pure_getters = compressor.option("pure_getters");
- return !pure_getters || this._throw_on_access(pure_getters);
+ return !compressor.option("pure_getters")
+ || this._dot_throw(compressor);
});
- function is_strict(pure_getters) {
- return /strict/.test(pure_getters);
+ function is_strict(compressor) {
+ return /strict/.test(compressor.option("pure_getters"));
}
def(AST_Node, is_strict);
@@ -1243,8 +1343,8 @@ merge(Compressor.prototype, {
def(AST_Undefined, return_true);
def(AST_Constant, return_false);
def(AST_Array, return_false);
- def(AST_Object, function(pure_getters) {
- if (!is_strict(pure_getters)) return false;
+ def(AST_Object, function(compressor) {
+ if (!is_strict(compressor)) return false;
for (var i = this.properties.length; --i >=0;)
if (this.properties[i].value instanceof AST_Accessor) return true;
return false;
@@ -1254,42 +1354,44 @@ merge(Compressor.prototype, {
def(AST_UnaryPrefix, function() {
return this.operator == "void";
});
- def(AST_Binary, function(pure_getters) {
+ def(AST_Binary, function(compressor) {
switch (this.operator) {
case "&&":
- return this.left._throw_on_access(pure_getters);
+ return this.left._dot_throw(compressor);
case "||":
- return this.left._throw_on_access(pure_getters)
- && this.right._throw_on_access(pure_getters);
+ return this.left._dot_throw(compressor)
+ && this.right._dot_throw(compressor);
default:
return false;
}
})
- def(AST_Assign, function(pure_getters) {
+ def(AST_Assign, function(compressor) {
return this.operator == "="
- && this.right._throw_on_access(pure_getters);
+ && this.right._dot_throw(compressor);
})
- def(AST_Conditional, function(pure_getters) {
- return this.consequent._throw_on_access(pure_getters)
- || this.alternative._throw_on_access(pure_getters);
+ def(AST_Conditional, function(compressor) {
+ return this.consequent._dot_throw(compressor)
+ || this.alternative._dot_throw(compressor);
})
- def(AST_Seq, function(pure_getters) {
- return this.cdr._throw_on_access(pure_getters);
+ def(AST_Sequence, function(compressor) {
+ return this.expressions[this.expressions.length - 1]._dot_throw(compressor);
});
- def(AST_SymbolRef, function(pure_getters) {
+ def(AST_SymbolRef, function(compressor) {
if (this.is_undefined) return true;
- if (!is_strict(pure_getters)) return false;
+ if (!is_strict(compressor)) return false;
+ if (is_undeclared_ref(this) && this.is_declared(compressor)) return false;
+ if (this.is_immutable()) return false;
var fixed = this.fixed_value();
- return !fixed || fixed._throw_on_access(pure_getters);
+ return !fixed || fixed._dot_throw(compressor);
});
})(function(node, func) {
- node.DEFMETHOD("_throw_on_access", func);
+ node.DEFMETHOD("_dot_throw", func);
});
/* -----[ boolean/negation helpers ]----- */
// methods to determine whether an expression has a boolean result type
- (function (def){
+ (function(def){
var unary_bool = [ "!", "delete" ];
var binary_bool = [ "in", "instanceof", "==", "!=", "===", "!==", "<", "<=", ">=", ">" ];
def(AST_Node, return_false);
@@ -1307,8 +1409,8 @@ merge(Compressor.prototype, {
def(AST_Assign, function(){
return this.operator == "=" && this.right.is_boolean();
});
- def(AST_Seq, function(){
- return this.cdr.is_boolean();
+ def(AST_Sequence, function(){
+ return this.expressions[this.expressions.length - 1].is_boolean();
});
def(AST_True, return_true);
def(AST_False, return_true);
@@ -1317,7 +1419,7 @@ merge(Compressor.prototype, {
});
// methods to determine if an expression has a numeric result type
- (function (def){
+ (function(def){
def(AST_Node, return_false);
def(AST_Number, return_true);
var unary = makePredicate("+ - ~ ++ --");
@@ -1334,8 +1436,8 @@ merge(Compressor.prototype, {
return binary(this.operator.slice(0, -1))
|| this.operator == "=" && this.right.is_number(compressor);
});
- def(AST_Seq, function(compressor){
- return this.cdr.is_number(compressor);
+ def(AST_Sequence, function(compressor){
+ return this.expressions[this.expressions.length - 1].is_number(compressor);
});
def(AST_Conditional, function(compressor){
return this.consequent.is_number(compressor) && this.alternative.is_number(compressor);
@@ -1345,7 +1447,7 @@ merge(Compressor.prototype, {
});
// methods to determine if an expression has a string result type
- (function (def){
+ (function(def){
def(AST_Node, return_false);
def(AST_String, return_true);
def(AST_UnaryPrefix, function(){
@@ -1358,8 +1460,8 @@ merge(Compressor.prototype, {
def(AST_Assign, function(compressor){
return (this.operator == "=" || this.operator == "+=") && this.right.is_string(compressor);
});
- def(AST_Seq, function(compressor){
- return this.cdr.is_string(compressor);
+ def(AST_Sequence, function(compressor){
+ return this.expressions[this.expressions.length - 1].is_string(compressor);
});
def(AST_Conditional, function(compressor){
return this.consequent.is_string(compressor) && this.alternative.is_string(compressor);
@@ -1375,7 +1477,7 @@ merge(Compressor.prototype, {
if (parent instanceof AST_Assign && parent.left === node) return node;
}
- (function (def){
+ (function(def){
AST_Node.DEFMETHOD("resolve_defines", function(compressor) {
if (!compressor.option("global_defs")) return;
var def = this._find_defs(compressor, "");
@@ -1401,7 +1503,7 @@ merge(Compressor.prototype, {
});
if (value && typeof value == "object") {
var props = [];
- for (var key in value) {
+ for (var key in value) if (HOP(value, key)) {
props.push(make_node(AST_ObjectKeyVal, orig, {
key: key,
value: to_node(value[key], orig)
@@ -1456,7 +1558,7 @@ merge(Compressor.prototype, {
}
// methods to evaluate a constant expression
- (function (def){
+ (function(def){
// If the node has been successfully reduced to a constant,
// then its value is returned; otherwise the element itself
// is returned.
@@ -1464,13 +1566,8 @@ merge(Compressor.prototype, {
// descendant of AST_Node.
AST_Node.DEFMETHOD("evaluate", function(compressor){
if (!compressor.option("evaluate")) return this;
- try {
- var val = this._eval(compressor);
- return !val || val instanceof RegExp || typeof val != "object" ? val : this;
- } catch(ex) {
- if (ex !== def) throw ex;
- return this;
- }
+ var val = this._eval(compressor);
+ return !val || val instanceof RegExp || typeof val != "object" ? val : this;
});
var unaryPrefix = makePredicate("! ~ - + void");
AST_Node.DEFMETHOD("is_constant", function(){
@@ -1516,27 +1613,28 @@ merge(Compressor.prototype, {
def(AST_Statement, function(){
throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start));
});
- def(AST_Lambda, function(){
- throw def;
- });
+ def(AST_Lambda, return_this);
function ev(node, compressor) {
if (!compressor) throw new Error("Compressor must be passed");
return node._eval(compressor);
};
- def(AST_Node, function(){
- throw def; // not constant
- });
+ def(AST_Node, return_this);
def(AST_Constant, function(){
return this.getValue();
});
def(AST_Array, function(compressor){
if (compressor.option("unsafe")) {
- return this.elements.map(function(element) {
- return ev(element, compressor);
- });
+ var elements = [];
+ for (var i = 0, len = this.elements.length; i < len; i++) {
+ var element = this.elements[i];
+ var value = ev(element, compressor);
+ if (element === value) return this;
+ elements.push(value);
+ }
+ return elements;
}
- throw def;
+ return this;
});
def(AST_Object, function(compressor){
if (compressor.option("unsafe")) {
@@ -1548,106 +1646,255 @@ merge(Compressor.prototype, {
key = key.name;
} else if (key instanceof AST_Node) {
key = ev(key, compressor);
+ if (key === prop.key) return this;
}
if (typeof Object.prototype[key] === 'function') {
- throw def;
+ return this;
}
val[key] = ev(prop.value, compressor);
+ if (val[key] === prop.value) return this;
}
return val;
}
- throw def;
+ return this;
});
def(AST_UnaryPrefix, function(compressor){
- var e = this.expression;
+ // Function would be evaluated to an array and so typeof would
+ // incorrectly return 'object'. Hence making is a special case.
+ if (this.operator == "typeof" && this.expression instanceof AST_Function) {
+ return typeof function(){};
+ }
+ var e = ev(this.expression, compressor);
+ if (e === this.expression) return this;
switch (this.operator) {
- case "!": return !ev(e, compressor);
+ case "!": return !e;
case "typeof":
- // Function would be evaluated to an array and so typeof would
- // incorrectly return 'object'. Hence making is a special case.
- if (e instanceof AST_Function) return typeof function(){};
-
- e = ev(e, compressor);
-
// typeof <RegExp> returns "object" or "function" on different platforms
// so cannot evaluate reliably
- if (e instanceof RegExp) throw def;
-
+ if (e instanceof RegExp) return this;
return typeof e;
- case "void": return void ev(e, compressor);
- case "~": return ~ev(e, compressor);
- case "-": return -ev(e, compressor);
- case "+": return +ev(e, compressor);
+ case "void": return void e;
+ case "~": return ~e;
+ case "-": return -e;
+ case "+": return +e;
}
- throw def;
+ return this;
});
- def(AST_Binary, function(c){
- var left = this.left, right = this.right, result;
+ def(AST_Binary, function(compressor){
+ var left = ev(this.left, compressor);
+ if (left === this.left) return this;
+ var right = ev(this.right, compressor);
+ if (right === this.right) return this;
+ var result;
switch (this.operator) {
- case "&&" : result = ev(left, c) && ev(right, c); break;
- case "||" : result = ev(left, c) || ev(right, c); break;
- case "|" : result = ev(left, c) | ev(right, c); break;
- case "&" : result = ev(left, c) & ev(right, c); break;
- case "^" : result = ev(left, c) ^ ev(right, c); break;
- case "+" : result = ev(left, c) + ev(right, c); break;
- case "*" : result = ev(left, c) * ev(right, c); break;
- case "/" : result = ev(left, c) / ev(right, c); break;
- case "%" : result = ev(left, c) % ev(right, c); break;
- case "-" : result = ev(left, c) - ev(right, c); break;
- case "<<" : result = ev(left, c) << ev(right, c); break;
- case ">>" : result = ev(left, c) >> ev(right, c); break;
- case ">>>" : result = ev(left, c) >>> ev(right, c); break;
- case "==" : result = ev(left, c) == ev(right, c); break;
- case "===" : result = ev(left, c) === ev(right, c); break;
- case "!=" : result = ev(left, c) != ev(right, c); break;
- case "!==" : result = ev(left, c) !== ev(right, c); break;
- case "<" : result = ev(left, c) < ev(right, c); break;
- case "<=" : result = ev(left, c) <= ev(right, c); break;
- case ">" : result = ev(left, c) > ev(right, c); break;
- case ">=" : result = ev(left, c) >= ev(right, c); break;
+ case "&&" : result = left && right; break;
+ case "||" : result = left || right; break;
+ case "|" : result = left | right; break;
+ case "&" : result = left & right; break;
+ case "^" : result = left ^ right; break;
+ case "+" : result = left + right; break;
+ case "*" : result = left * right; break;
+ case "/" : result = left / right; break;
+ case "%" : result = left % right; break;
+ case "-" : result = left - right; break;
+ case "<<" : result = left << right; break;
+ case ">>" : result = left >> right; break;
+ case ">>>" : result = left >>> right; break;
+ case "==" : result = left == right; break;
+ case "===" : result = left === right; break;
+ case "!=" : result = left != right; break;
+ case "!==" : result = left !== right; break;
+ case "<" : result = left < right; break;
+ case "<=" : result = left <= right; break;
+ case ">" : result = left > right; break;
+ case ">=" : result = left >= right; break;
default:
- throw def;
+ return this;
}
- if (isNaN(result) && c.find_parent(AST_With)) {
+ if (isNaN(result) && compressor.find_parent(AST_With)) {
// leave original expression as is
- throw def;
+ return this;
}
return result;
});
def(AST_Conditional, function(compressor){
- return ev(this.condition, compressor)
- ? ev(this.consequent, compressor)
- : ev(this.alternative, compressor);
+ var condition = ev(this.condition, compressor);
+ if (condition === this.condition) return this;
+ var node = condition ? this.consequent : this.alternative;
+ var value = ev(node, compressor);
+ return value === node ? this : value;
});
def(AST_SymbolRef, function(compressor){
- if (!compressor.option("reduce_vars") || this._evaluating) throw def;
- this._evaluating = true;
- try {
- var fixed = this.fixed_value();
- if (!fixed) throw def;
- var value = ev(fixed, compressor);
- if (!HOP(fixed, "_eval")) fixed._eval = function() {
- return value;
- };
- if (value && typeof value == "object" && this.definition().escaped) throw def;
+ if (!compressor.option("reduce_vars")) return this;
+ var fixed = this.fixed_value();
+ if (!fixed) return this;
+ this._eval = return_this;
+ var value = ev(fixed, compressor);
+ if (value === fixed) {
+ delete this._eval;
+ return this;
+ }
+ if (!HOP(fixed, "_eval")) fixed._eval = function() {
return value;
- } finally {
- this._evaluating = false;
+ };
+ if (value && typeof value == "object" && this.definition().escaped) {
+ delete this._eval;
+ return this;
}
+ this._eval = fixed._eval;
+ return value;
});
+ var global_objs = {
+ Array: Array,
+ Math: Math,
+ Number: Number,
+ String: String,
+ };
+ function convert_to_predicate(obj) {
+ for (var key in obj) {
+ obj[key] = makePredicate(obj[key]);
+ }
+ }
+ var static_values = {
+ Math: [
+ "E",
+ "LN10",
+ "LN2",
+ "LOG2E",
+ "LOG10E",
+ "PI",
+ "SQRT1_2",
+ "SQRT2",
+ ],
+ Number: [
+ "MAX_VALUE",
+ "MIN_VALUE",
+ "NaN",
+ "NEGATIVE_INFINITY",
+ "POSITIVE_INFINITY",
+ ],
+ };
+ convert_to_predicate(static_values);
def(AST_PropAccess, function(compressor){
if (compressor.option("unsafe")) {
var key = this.property;
if (key instanceof AST_Node) {
key = ev(key, compressor);
+ if (key === this.property) return this;
+ }
+ var exp = this.expression;
+ var val;
+ if (is_undeclared_ref(exp)) {
+ if (!(static_values[exp.name] || return_false)(key)) return this;
+ val = global_objs[exp.name];
+ } else {
+ val = ev(exp, compressor);
+ if (!val || val === exp || !HOP(val, key)) return this;
+ }
+ return val[key];
+ }
+ return this;
+ });
+ var object_fns = [
+ "constructor",
+ "toString",
+ "valueOf",
+ ];
+ var native_fns = {
+ Array: [
+ "indexOf",
+ "join",
+ "lastIndexOf",
+ "slice",
+ ].concat(object_fns),
+ Boolean: object_fns,
+ Number: [
+ "toExponential",
+ "toFixed",
+ "toPrecision",
+ ].concat(object_fns),
+ RegExp: [
+ "test",
+ ].concat(object_fns),
+ String: [
+ "charAt",
+ "charCodeAt",
+ "concat",
+ "indexOf",
+ "italics",
+ "lastIndexOf",
+ "match",
+ "replace",
+ "search",
+ "slice",
+ "split",
+ "substr",
+ "substring",
+ "trim",
+ ].concat(object_fns),
+ };
+ convert_to_predicate(native_fns);
+ var static_fns = {
+ Array: [
+ "isArray",
+ ],
+ Math: [
+ "abs",
+ "acos",
+ "asin",
+ "atan",
+ "ceil",
+ "cos",
+ "exp",
+ "floor",
+ "log",
+ "round",
+ "sin",
+ "sqrt",
+ "tan",
+ "atan2",
+ "pow",
+ "max",
+ "min"
+ ],
+ Number: [
+ "isFinite",
+ "isNaN",
+ ],
+ String: [
+ "fromCharCode",
+ ],
+ };
+ convert_to_predicate(static_fns);
+ def(AST_Call, function(compressor){
+ var exp = this.expression;
+ if (compressor.option("unsafe") && exp instanceof AST_PropAccess) {
+ var key = exp.property;
+ if (key instanceof AST_Node) {
+ key = ev(key, compressor);
+ if (key === exp.property) return this;
+ }
+ var val;
+ var e = exp.expression;
+ if (is_undeclared_ref(e)) {
+ if (!(static_fns[e.name] || return_false)(key)) return this;
+ val = global_objs[e.name];
+ } else {
+ val = ev(e, compressor);
+ if (val === e || !(val && native_fns[val.constructor.name] || return_false)(key)) return this;
}
- var val = ev(this.expression, compressor);
- if (val && HOP(val, key)) {
- return val[key];
+ var args = [];
+ for (var i = 0, len = this.args.length; i < len; i++) {
+ var arg = this.args[i];
+ var value = ev(arg, compressor);
+ if (arg === value) return this;
+ args.push(value);
}
+ return val[key].apply(val, args);
}
- throw def;
+ return this;
});
+ def(AST_New, return_this);
})(function(node, func){
node.DEFMETHOD("_eval", func);
});
@@ -1684,10 +1931,10 @@ merge(Compressor.prototype, {
return this.expression;
return basic_negation(this);
});
- def(AST_Seq, function(compressor){
- var self = this.clone();
- self.cdr = self.cdr.negate(compressor);
- return self;
+ def(AST_Sequence, function(compressor){
+ var expressions = this.expressions.slice();
+ expressions.push(expressions.pop().negate(compressor));
+ return make_sequence(this, expressions);
});
def(AST_Conditional, function(compressor, first_in_statement){
var self = this.clone();
@@ -1811,8 +2058,9 @@ merge(Compressor.prototype, {
|| this.expression.has_side_effects(compressor);
});
def(AST_SymbolRef, function(compressor){
- return this.undeclared();
+ return !this.is_declared(compressor);
});
+ def(AST_SymbolDeclaration, return_false);
def(AST_Object, function(compressor){
return any(this.properties, compressor);
});
@@ -1831,14 +2079,44 @@ merge(Compressor.prototype, {
|| this.expression.has_side_effects(compressor)
|| this.property.has_side_effects(compressor);
});
- def(AST_Seq, function(compressor){
- return this.car.has_side_effects(compressor)
- || this.cdr.has_side_effects(compressor);
+ def(AST_Sequence, function(compressor){
+ return this.expressions.some(function(expression, index) {
+ return expression.has_side_effects(compressor);
+ });
});
})(function(node, func){
node.DEFMETHOD("has_side_effects", func);
});
+ // determine if expression is constant
+ (function(def){
+ function all(list) {
+ for (var i = list.length; --i >= 0;)
+ if (!list[i].is_constant_expression())
+ return false;
+ return true;
+ }
+ def(AST_Node, return_false);
+ def(AST_Constant, return_true);
+ def(AST_Unary, function(){
+ return this.expression.is_constant_expression();
+ });
+ def(AST_Binary, function(){
+ return this.left.is_constant_expression() && this.right.is_constant_expression();
+ });
+ def(AST_Array, function(){
+ return all(this.elements);
+ });
+ def(AST_Object, function(){
+ return all(this.properties);
+ });
+ def(AST_ObjectProperty, function(){
+ return this.value.is_constant_expression();
+ });
+ })(function(node, func){
+ node.DEFMETHOD("is_constant_expression", func);
+ });
+
// tell me if a statement aborts
function aborts(thing) {
return thing && thing.aborts();
@@ -1883,12 +2161,12 @@ merge(Compressor.prototype, {
});
OPT(AST_Block, function(self, compressor){
- self.body = tighten_body(self.body, compressor);
+ tighten_body(self.body, compressor);
return self;
});
OPT(AST_BlockStatement, function(self, compressor){
- self.body = tighten_body(self.body, compressor);
+ tighten_body(self.body, compressor);
switch (self.body.length) {
case 1: return self.body[0];
case 0: return make_node(AST_EmptyStatement, self);
@@ -1897,269 +2175,290 @@ merge(Compressor.prototype, {
});
AST_Scope.DEFMETHOD("drop_unused", function(compressor){
+ if (!compressor.option("unused")) return;
+ if (compressor.has_directive("use asm")) return;
var self = this;
- if (compressor.has_directive("use asm")) return self;
- var toplevel = compressor.option("toplevel");
- if (compressor.option("unused")
- && (!(self instanceof AST_Toplevel) || toplevel)
- && !self.uses_eval
- && !self.uses_with) {
- var assign_as_unused = !/keep_assign/.test(compressor.option("unused"));
- var drop_funcs = /funcs/.test(toplevel);
- var drop_vars = /vars/.test(toplevel);
- if (!(self instanceof AST_Toplevel) || toplevel == true) {
- drop_funcs = drop_vars = true;
- }
- var in_use = [];
- var in_use_ids = Object.create(null); // avoid expensive linear scans of in_use
- if (self instanceof AST_Toplevel && compressor.top_retain) {
- self.variables.each(function(def) {
- if (compressor.top_retain(def) && !(def.id in in_use_ids)) {
- in_use_ids[def.id] = true;
- in_use.push(def);
+ if (self.uses_eval || self.uses_with) return;
+ var drop_funcs = !(self instanceof AST_Toplevel) || compressor.toplevel.funcs;
+ var drop_vars = !(self instanceof AST_Toplevel) || compressor.toplevel.vars;
+ if (!drop_funcs && !drop_vars) return;
+ var assign_as_unused = /keep_assign/.test(compressor.option("unused")) ? return_false : function(node) {
+ if (node instanceof AST_Assign && (node.write_only || node.operator == "=")) {
+ return node.left;
+ }
+ if (node instanceof AST_Unary && node.write_only) return node.expression;
+ };
+ var in_use = [];
+ var in_use_ids = Object.create(null); // avoid expensive linear scans of in_use
+ if (self instanceof AST_Toplevel && compressor.top_retain) {
+ self.variables.each(function(def) {
+ if (compressor.top_retain(def) && !(def.id in in_use_ids)) {
+ in_use_ids[def.id] = true;
+ in_use.push(def);
+ }
+ });
+ }
+ var var_defs_by_id = new Dictionary();
+ 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 (!drop_funcs && scope === self) {
+ var node_def = node.name.definition();
+ if (!(node_def.id in in_use_ids)) {
+ in_use_ids[node_def.id] = true;
+ in_use.push(node_def);
+ }
}
- });
- }
- 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 (!drop_funcs && scope === self) {
- var node_def = node.name.definition();
+ initializations.add(node.name.name, node);
+ return true; // don't go in nested scopes
+ }
+ if (node instanceof AST_Definitions && scope === self) {
+ node.definitions.forEach(function(def){
+ var node_def = def.name.definition();
+ if (def.name instanceof AST_SymbolVar) {
+ var_defs_by_id.add(node_def.id, def);
+ }
+ if (!drop_vars) {
if (!(node_def.id in in_use_ids)) {
in_use_ids[node_def.id] = true;
in_use.push(node_def);
}
}
- initializations.add(node.name.name, node);
- return true; // don't go in nested scopes
- }
- if (node instanceof AST_Definitions && scope === self) {
- node.definitions.forEach(function(def){
- if (!drop_vars) {
- var node_def = def.name.definition();
- if (!(node_def.id in in_use_ids)) {
- in_use_ids[node_def.id] = true;
- in_use.push(node_def);
- }
+ if (def.value) {
+ initializations.add(def.name.name, def.value);
+ if (def.value.has_side_effects(compressor)) {
+ def.value.walk(tw);
}
- if (def.value) {
- initializations.add(def.name.name, def.value);
- if (def.value.has_side_effects(compressor)) {
- def.value.walk(tw);
- }
- }
- });
- return true;
- }
- if (assign_as_unused
- && node instanceof AST_Assign
- && node.operator == "="
- && node.left instanceof AST_SymbolRef
- && !is_reference_const(node.left)
- && scope === self) {
- node.right.walk(tw);
- return true;
- }
- if (node instanceof AST_SymbolRef) {
- 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) {
- var save_scope = scope;
- scope = node;
- descend();
- scope = save_scope;
- return true;
+ });
+ return true;
+ }
+ if (assign_as_unused(node) instanceof AST_SymbolRef && scope === self) {
+ if (node instanceof AST_Assign) node.right.walk(tw);
+ return true;
+ }
+ if (node instanceof AST_SymbolRef) {
+ 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;
}
- });
- self.walk(tw);
- // pass 2: for every used symbol we need to walk its
- // initialization code to figure out if it uses other
- // symbols (that may not be in_use).
- for (var i = 0; i < in_use.length; ++i) {
- in_use[i].orig.forEach(function(decl){
- // undeclared globals will be instanceof AST_SymbolRef
- var init = initializations.get(decl.name);
- if (init) init.forEach(function(init){
- var tw = new TreeWalker(function(node){
- if (node instanceof AST_SymbolRef) {
- 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);
- }
+ if (node instanceof AST_Scope) {
+ var save_scope = scope;
+ scope = node;
+ descend();
+ scope = save_scope;
+ return true;
+ }
+ }
+ });
+ self.walk(tw);
+ // pass 2: for every used symbol we need to walk its
+ // initialization code to figure out if it uses other
+ // symbols (that may not be in_use).
+ for (var i = 0; i < in_use.length; ++i) {
+ in_use[i].orig.forEach(function(decl){
+ // undeclared globals will be instanceof AST_SymbolRef
+ var init = initializations.get(decl.name);
+ if (init) init.forEach(function(init){
+ var tw = new TreeWalker(function(node){
+ if (node instanceof AST_SymbolRef) {
+ var node_def = node.definition();
+ if (!(node_def.id in in_use_ids)) {
+ in_use_ids[node_def.id] = true;
+ in_use.push(node_def);
}
- });
- init.walk(tw);
+ }
});
+ init.walk(tw);
});
- }
- // pass 3: we should drop declarations not in_use
- var tt = new TreeTransformer(
- function before(node, descend, in_list) {
- if (node instanceof AST_Function
- && node.name
- && !compressor.option("keep_fnames")) {
- var def = node.name.definition();
- // any declarations with same name will overshadow
- // name of this anonymous function and can therefore
- // never be used anywhere
- if (!(def.id in in_use_ids) || def.orig.length > 1)
- node.name = null;
- }
- if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) {
- var trim = !compressor.option("keep_fargs");
- for (var a = node.argnames, i = a.length; --i >= 0;) {
- var sym = a[i];
- if (!(sym.definition().id in in_use_ids)) {
- sym.__unused = true;
- if (trim) {
- a.pop();
- compressor[sym.unreferenced() ? "warn" : "info"]("Dropping unused function argument {name} [{file}:{line},{col}]", {
- name : sym.name,
- file : sym.start.file,
- line : sym.start.line,
- col : sym.start.col
- });
- }
- }
- else {
- trim = false;
+ });
+ }
+ // pass 3: we should drop declarations not in_use
+ var tt = new TreeTransformer(
+ function before(node, descend, in_list) {
+ if (node instanceof AST_Function
+ && node.name
+ && !compressor.option("keep_fnames")) {
+ var def = node.name.definition();
+ // any declarations with same name will overshadow
+ // name of this anonymous function and can therefore
+ // never be used anywhere
+ if (!(def.id in in_use_ids) || def.orig.length > 1)
+ node.name = null;
+ }
+ if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) {
+ var trim = !compressor.option("keep_fargs");
+ for (var a = node.argnames, i = a.length; --i >= 0;) {
+ var sym = a[i];
+ if (!(sym.definition().id in in_use_ids)) {
+ sym.__unused = true;
+ if (trim) {
+ a.pop();
+ compressor[sym.unreferenced() ? "warn" : "info"]("Dropping unused function argument {name} [{file}:{line},{col}]", template(sym));
}
}
- }
- if (drop_funcs && node instanceof AST_Defun && node !== self) {
- if (!(node.name.definition().id in in_use_ids)) {
- compressor[node.name.unreferenced() ? "warn" : "info"]("Dropping unused function {name} [{file}:{line},{col}]", {
- name : node.name.name,
- file : node.name.start.file,
- line : node.name.start.line,
- col : node.name.start.col
- });
- return make_node(AST_EmptyStatement, node);
+ else {
+ trim = false;
}
- return node;
}
- if (drop_vars && node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn && tt.parent().init === node)) {
- var def = node.definitions.filter(function(def){
- if (def.value) def.value = def.value.transform(tt);
- var sym = def.name.definition();
- if (sym.id in in_use_ids) return true;
- if (sym.orig[0] instanceof AST_SymbolCatch) {
- def.value = def.value && def.value.drop_side_effect_free(compressor);
- return true;
- }
- var w = {
- name : def.name.name,
- file : def.name.start.file,
- line : def.name.start.line,
- col : def.name.start.col
- };
- if (def.value && (def._unused_side_effects = def.value.drop_side_effect_free(compressor))) {
- compressor.warn("Side effects in initialization of unused variable {name} [{file}:{line},{col}]", w);
- return true;
+ }
+ if (drop_funcs && node instanceof AST_Defun && node !== self) {
+ if (!(node.name.definition().id in in_use_ids)) {
+ compressor[node.name.unreferenced() ? "warn" : "info"]("Dropping unused function {name} [{file}:{line},{col}]", template(node.name));
+ return make_node(AST_EmptyStatement, node);
+ }
+ return node;
+ }
+ if (drop_vars && node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn && tt.parent().init === node)) {
+ // place uninitialized names at the start
+ var body = [], head = [], tail = [];
+ // for unused names whose initialization has
+ // side effects, we can cascade the init. code
+ // into the next one, or next statement.
+ var side_effects = [];
+ node.definitions.forEach(function(def) {
+ if (def.value) def.value = def.value.transform(tt);
+ var sym = def.name.definition();
+ if (sym.id in in_use_ids) {
+ if (def.name instanceof AST_SymbolVar) {
+ var var_defs = var_defs_by_id.get(sym.id);
+ if (var_defs.length > 1 && !def.value) {
+ compressor.warn("Dropping duplicated definition of variable {name} [{file}:{line},{col}]", template(def.name));
+ remove(var_defs, def);
+ remove(sym.orig, def.name);
+ return;
+ }
}
- compressor[def.name.unreferenced() ? "warn" : "info"]("Dropping unused variable {name} [{file}:{line},{col}]", w);
- return false;
- });
- // place uninitialized names at the start
- def = mergeSort(def, function(a, b){
- if (!a.value && b.value) return -1;
- if (!b.value && a.value) return 1;
- return 0;
- });
- // for unused names whose initialization has
- // side effects, we can cascade the init. code
- // into the next one, or next statement.
- var side_effects = [];
- for (var i = 0; i < def.length;) {
- var x = def[i];
- if (x._unused_side_effects) {
- side_effects.push(x._unused_side_effects);
- def.splice(i, 1);
- } else {
+ if (def.value) {
if (side_effects.length > 0) {
- side_effects.push(x.value);
- x.value = AST_Seq.from_array(side_effects);
+ if (tail.length > 0) {
+ merge_sequence(side_effects, def.value);
+ def.value = make_sequence(def.value, side_effects);
+ } else {
+ body.push(make_node(AST_SimpleStatement, node, {
+ body: make_sequence(node, side_effects)
+ }));
+ }
side_effects = [];
}
- ++i;
+ tail.push(def);
+ } else {
+ head.push(def);
}
- }
- if (side_effects.length > 0) {
- side_effects = make_node(AST_BlockStatement, node, {
- body: [ make_node(AST_SimpleStatement, node, {
- body: AST_Seq.from_array(side_effects)
- }) ]
- });
+ } else if (sym.orig[0] instanceof AST_SymbolCatch) {
+ var value = def.value && def.value.drop_side_effect_free(compressor);
+ if (value) merge_sequence(side_effects, value);
+ def.value = null;
+ head.push(def);
} else {
- side_effects = null;
- }
- if (def.length == 0 && !side_effects) {
- return make_node(AST_EmptyStatement, node);
- }
- if (def.length == 0) {
- return in_list ? MAP.splice(side_effects.body) : side_effects;
+ var value = def.value && def.value.drop_side_effect_free(compressor);
+ if (value) {
+ compressor.warn("Side effects in initialization of unused variable {name} [{file}:{line},{col}]", template(def.name));
+ merge_sequence(side_effects, value);
+ } else {
+ compressor[def.name.unreferenced() ? "warn" : "info"]("Dropping unused variable {name} [{file}:{line},{col}]", template(def.name));
+ }
+ remove(sym.orig, def.name);
}
- node.definitions = def;
- if (side_effects) {
- side_effects.body.unshift(node);
- return in_list ? MAP.splice(side_effects.body) : side_effects;
+ });
+ if (head.length == 0 && tail.length == 1 && tail[0].name instanceof AST_SymbolVar) {
+ var var_defs = var_defs_by_id.get(tail[0].name.definition().id);
+ if (var_defs.length > 1) {
+ var def = tail.pop();
+ compressor.warn("Converting duplicated definition of variable {name} to assignment [{file}:{line},{col}]", template(def.name));
+ remove(var_defs, def);
+ remove(def.name.definition().orig, def.name);
+ side_effects.unshift(make_node(AST_Assign, def, {
+ operator: "=",
+ left: make_node(AST_SymbolRef, def.name, def.name),
+ right: def.value
+ }));
}
- return node;
}
- if (drop_vars && assign_as_unused
- && node instanceof AST_Assign
- && node.operator == "="
- && node.left instanceof AST_SymbolRef) {
- var def = node.left.definition();
- if (!(def.id in in_use_ids)
- && self.variables.get(def.name) === def) {
+ if (head.length > 0 || tail.length > 0) {
+ node.definitions = head.concat(tail);
+ body.push(node);
+ }
+ if (side_effects.length > 0) {
+ body.push(make_node(AST_SimpleStatement, node, {
+ body: make_sequence(node, side_effects)
+ }));
+ }
+ switch (body.length) {
+ case 0:
+ return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
+ case 1:
+ return body[0];
+ default:
+ return in_list ? MAP.splice(body) : make_node(AST_BlockStatement, node, {
+ body: body
+ });
+ }
+ }
+ if (drop_vars) {
+ var def = assign_as_unused(node);
+ if (def instanceof AST_SymbolRef
+ && !((def = def.definition()).id in in_use_ids)
+ && self.variables.get(def.name) === def) {
+ if (node instanceof AST_Assign) {
return maintain_this_binding(tt.parent(), node, node.right.transform(tt));
}
+ return make_node(AST_Number, node, {
+ value: 0
+ });
}
- // certain combination of unused name + side effect leads to:
- // https://github.com/mishoo/UglifyJS2/issues/44
- // https://github.com/mishoo/UglifyJS2/issues/1830
- // that's an invalid AST.
- // We fix it at this stage by moving the `var` outside the `for`.
- if (node instanceof AST_For) {
- descend(node, this);
- if (node.init instanceof AST_BlockStatement) {
- var block = node.init;
- node.init = block.body.pop();
- block.body.push(node);
- return in_list ? MAP.splice(block.body) : block;
- } else if (is_empty(node.init)) {
- node.init = null;
- }
- return node;
+ }
+ // certain combination of unused name + side effect leads to:
+ // https://github.com/mishoo/UglifyJS2/issues/44
+ // https://github.com/mishoo/UglifyJS2/issues/1830
+ // https://github.com/mishoo/UglifyJS2/issues/1838
+ // that's an invalid AST.
+ // We fix it at this stage by moving the `var` outside the `for`.
+ if (node instanceof AST_For) {
+ descend(node, this);
+ if (node.init instanceof AST_BlockStatement) {
+ var block = node.init;
+ node.init = block.body.pop();
+ block.body.push(node);
+ return in_list ? MAP.splice(block.body) : block;
+ } else if (node.init instanceof AST_SimpleStatement) {
+ node.init = node.init.body;
+ } else if (is_empty(node.init)) {
+ node.init = null;
}
- if (node instanceof AST_LabeledStatement && node.body instanceof AST_For) {
- descend(node, this);
- if (node.body instanceof AST_BlockStatement) {
- var block = node.body;
- node.body = block.body.pop();
- block.body.push(node);
- return in_list ? MAP.splice(block.body) : block;
- }
- return node;
+ return node;
+ }
+ if (node instanceof AST_LabeledStatement && node.body instanceof AST_For) {
+ descend(node, this);
+ if (node.body instanceof AST_BlockStatement) {
+ var block = node.body;
+ node.body = block.body.pop();
+ block.body.push(node);
+ return in_list ? MAP.splice(block.body) : block;
}
- if (node instanceof AST_Scope && node !== self)
- return node;
+ return node;
}
- );
- self.transform(tt);
- }
+ if (node instanceof AST_Scope && node !== self)
+ return node;
+
+ function template(sym) {
+ return {
+ name : sym.name,
+ file : sym.start.file,
+ line : sym.start.line,
+ col : sym.start.col
+ };
+ }
+ }
+ );
+ self.transform(tt);
});
AST_Scope.DEFMETHOD("hoist_declarations", function(compressor){
@@ -2189,11 +2488,12 @@ merge(Compressor.prototype, {
dirs.push(node);
return make_node(AST_EmptyStatement, node);
}
- if (node instanceof AST_Defun && hoist_funs) {
+ if (hoist_funs && node instanceof AST_Defun
+ && (tt.parent() === self || !compressor.has_directive("use strict"))) {
hoisted.push(node);
return make_node(AST_EmptyStatement, node);
}
- if (node instanceof AST_Var && hoist_vars) {
+ if (hoist_vars && node instanceof AST_Var) {
node.definitions.forEach(function(def){
vars.set(def.name.name, def);
++vars_found;
@@ -2254,8 +2554,8 @@ merge(Compressor.prototype, {
self.body.splice(i, 1);
continue;
}
- if (expr instanceof AST_Seq
- && (assign = expr.car) instanceof AST_Assign
+ if (expr instanceof AST_Sequence
+ && (assign = expr.expressions[0]) instanceof AST_Assign
&& assign.operator == "="
&& (sym = assign.left) instanceof AST_Symbol
&& vars.has(sym.name))
@@ -2265,7 +2565,7 @@ merge(Compressor.prototype, {
def.value = assign.right;
remove(defs, def);
defs.push(def);
- self.body[i].body = expr.cdr;
+ self.body[i].body = make_sequence(expr, expr.expressions.slice(1));
continue;
}
}
@@ -2299,12 +2599,14 @@ merge(Compressor.prototype, {
// if all elements were dropped. Note: original array may be
// returned if nothing changed.
function trim(nodes, compressor, first_in_statement) {
+ var len = nodes.length;
+ if (!len) return null;
var ret = [], changed = false;
- for (var i = 0, len = nodes.length; i < len; i++) {
+ for (var i = 0; i < len; i++) {
var node = nodes[i].drop_side_effect_free(compressor, first_in_statement);
changed |= node !== nodes[i];
if (node) {
- ret.push(node);
+ merge_sequence(ret, node);
first_in_statement = false;
}
}
@@ -2319,7 +2621,7 @@ merge(Compressor.prototype, {
if (this.expression instanceof AST_Function
&& (!this.expression.name || !this.expression.name.definition().references.length)) {
var node = this.clone();
- node.expression = node.expression.process_expression(false, compressor);
+ node.expression.process_expression(false, compressor);
return node;
}
return this;
@@ -2329,7 +2631,7 @@ merge(Compressor.prototype, {
this.pure.value = this.pure.value.replace(/[@#]__PURE__/g, ' ');
}
var args = trim(this.args, compressor, first_in_statement);
- return args && AST_Seq.from_array(args);
+ return args && make_sequence(this, args);
});
def(AST_Accessor, return_null);
def(AST_Function, return_null);
@@ -2346,13 +2648,13 @@ merge(Compressor.prototype, {
default:
var left = this.left.drop_side_effect_free(compressor, first_in_statement);
if (!left) return this.right.drop_side_effect_free(compressor, first_in_statement);
- return make_node(AST_Seq, this, {
- car: left,
- cdr: right
- });
+ return make_sequence(this, [ left, right ]);
}
});
- def(AST_Assign, return_this);
+ def(AST_Assign, function(compressor){
+ this.write_only = !this.left.has_side_effects(compressor);
+ return this;
+ });
def(AST_Conditional, function(compressor){
var consequent = this.consequent.drop_side_effect_free(compressor);
var alternative = this.alternative.drop_side_effect_free(compressor);
@@ -2373,7 +2675,10 @@ merge(Compressor.prototype, {
return node;
});
def(AST_Unary, function(compressor, first_in_statement){
- if (unary_side_effects(this.operator)) return this;
+ if (unary_side_effects(this.operator)) {
+ this.write_only = !this.expression.has_side_effects(compressor);
+ return this;
+ }
if (this.operator == "typeof" && this.expression instanceof AST_SymbolRef) return null;
var expression = this.expression.drop_side_effect_free(compressor, first_in_statement);
if (first_in_statement
@@ -2387,19 +2692,19 @@ merge(Compressor.prototype, {
}
return expression;
});
- def(AST_SymbolRef, function() {
- return this.undeclared() ? this : null;
+ def(AST_SymbolRef, function(compressor) {
+ return this.is_declared(compressor) ? null : this;
});
def(AST_Object, function(compressor, first_in_statement){
var values = trim(this.properties, compressor, first_in_statement);
- return values && AST_Seq.from_array(values);
+ return values && make_sequence(this, values);
});
def(AST_ObjectProperty, function(compressor, first_in_statement){
return this.value.drop_side_effect_free(compressor, first_in_statement);
});
def(AST_Array, function(compressor, first_in_statement){
var values = trim(this.elements, compressor, first_in_statement);
- return values && AST_Seq.from_array(values);
+ return values && make_sequence(this, values);
});
def(AST_Dot, function(compressor, first_in_statement){
if (this.expression.may_throw_on_access(compressor)) return this;
@@ -2411,19 +2716,15 @@ merge(Compressor.prototype, {
if (!expression) return this.property.drop_side_effect_free(compressor, first_in_statement);
var property = this.property.drop_side_effect_free(compressor);
if (!property) return expression;
- return make_node(AST_Seq, this, {
- car: expression,
- cdr: property
- });
+ return make_sequence(this, [ expression, property ]);
});
- def(AST_Seq, function(compressor){
- var cdr = this.cdr.drop_side_effect_free(compressor);
- if (cdr === this.cdr) return this;
- if (!cdr) return this.car;
- return make_node(AST_Seq, this, {
- car: this.car,
- cdr: cdr
- });
+ def(AST_Sequence, function(compressor){
+ var last = this.expressions[this.expressions.length - 1];
+ var expr = last.drop_side_effect_free(compressor);
+ if (expr === last) return this;
+ var expressions = this.expressions.slice(0, -1);
+ if (expr) merge_sequence(expressions, expr);
+ return make_sequence(this, expressions);
});
})(function(node, func){
node.DEFMETHOD("drop_side_effect_free", func);
@@ -2782,7 +3083,7 @@ merge(Compressor.prototype, {
});
OPT(AST_Try, function(self, compressor){
- self.body = tighten_body(self.body, compressor);
+ tighten_body(self.body, compressor);
if (self.bcatch && self.bfinally && all(self.bfinally.body, is_empty)) self.bfinally = null;
if (all(self.body, is_empty)) {
var body = [];
@@ -2814,7 +3115,7 @@ merge(Compressor.prototype, {
return a;
}, []);
if (assignments.length == 0) return null;
- return AST_Seq.from_array(assignments);
+ return make_sequence(this, assignments);
});
OPT(AST_Definitions, function(self, compressor){
@@ -2825,33 +3126,18 @@ merge(Compressor.prototype, {
OPT(AST_Call, function(self, compressor){
var exp = self.expression;
- if (compressor.option("reduce_vars")
- && exp instanceof AST_SymbolRef) {
- var def = exp.definition();
- var fixed = exp.fixed_value();
- if (fixed instanceof AST_Defun) {
- def.fixed = fixed = make_node(AST_Function, fixed, fixed).clone(true);
- }
- if (fixed instanceof AST_Function) {
- exp = fixed;
- if (compressor.option("unused")
- && def.references.length == 1
- && !(def.scope.uses_arguments
- && def.orig[0] instanceof AST_SymbolFunarg)
- && !def.scope.uses_eval
- && compressor.find_parent(AST_Scope) === def.scope) {
- self.expression = exp;
- }
- }
- }
+ var fn = exp;
if (compressor.option("unused")
- && exp instanceof AST_Function
- && !exp.uses_arguments
- && !exp.uses_eval) {
+ && (fn instanceof AST_Function
+ || compressor.option("reduce_vars")
+ && fn instanceof AST_SymbolRef
+ && (fn = fn.fixed_value()) instanceof AST_Function)
+ && !fn.uses_arguments
+ && !fn.uses_eval) {
var pos = 0, last = 0;
for (var i = 0, len = self.args.length; i < len; i++) {
- var trim = i >= exp.argnames.length;
- if (trim || exp.argnames[i].__unused) {
+ var trim = i >= fn.argnames.length;
+ if (trim || fn.argnames[i].__unused) {
var node = self.args[i].drop_side_effect_free(compressor);
if (node) {
self.args[pos++] = node;
@@ -2869,7 +3155,7 @@ merge(Compressor.prototype, {
self.args.length = last;
}
if (compressor.option("unsafe")) {
- if (exp instanceof AST_SymbolRef && exp.undeclared()) {
+ if (is_undeclared_ref(exp)) {
switch (exp.name) {
case "Array":
if (self.args.length != 1) {
@@ -2913,62 +3199,6 @@ merge(Compressor.prototype, {
operator: "!"
}).optimize(compressor);
break;
- case "Function":
- // new Function() => function(){}
- if (self.args.length == 0) return make_node(AST_Function, self, {
- argnames: [],
- body: []
- });
- if (all(self.args, function(x){ return x instanceof AST_String })) {
- // quite a corner-case, but we can handle it:
- // https://github.com/mishoo/UglifyJS2/issues/203
- // if the code argument is a constant, then we can minify it.
- try {
- var code = "(function(" + self.args.slice(0, -1).map(function(arg){
- return arg.value;
- }).join(",") + "){" + self.args[self.args.length - 1].value + "})()";
- var ast = parse(code);
- ast.figure_out_scope({ screw_ie8: compressor.option("screw_ie8") });
- var comp = new Compressor(compressor.options);
- ast = ast.transform(comp);
- ast.figure_out_scope({ screw_ie8: compressor.option("screw_ie8") });
- ast.mangle_names();
- var fun;
- try {
- ast.walk(new TreeWalker(function(node){
- if (node instanceof AST_Lambda) {
- fun = node;
- throw ast;
- }
- }));
- } catch(ex) {
- if (ex !== ast) throw ex;
- };
- if (!fun) return self;
- var args = fun.argnames.map(function(arg, i){
- return make_node(AST_String, self.args[i], {
- value: arg.print_to_string()
- });
- });
- var code = OutputStream();
- AST_BlockStatement.prototype._codegen.call(fun, fun, code);
- code = code.toString().replace(/^\{|\}$/g, "");
- args.push(make_node(AST_String, self.args[self.args.length - 1], {
- value: code
- }));
- self.args = args;
- return self;
- } catch(ex) {
- if (ex instanceof JS_Parse_Error) {
- compressor.warn("Error parsing code passed to new Function [{file}:{line},{col}]", self.args[self.args.length - 1].start);
- compressor.warn(ex.toString());
- } else {
- console.log(ex);
- throw ex;
- }
- }
- }
- break;
}
}
else if (exp instanceof AST_Dot && exp.property == "toString" && self.args.length == 0) {
@@ -3051,17 +3281,116 @@ merge(Compressor.prototype, {
}
}
}
+ if (compressor.option("unsafe_Func")
+ && is_undeclared_ref(exp)
+ && exp.name == "Function") {
+ // new Function() => function(){}
+ if (self.args.length == 0) return make_node(AST_Function, self, {
+ argnames: [],
+ body: []
+ });
+ if (all(self.args, function(x) {
+ return x instanceof AST_String;
+ })) {
+ // quite a corner-case, but we can handle it:
+ // https://github.com/mishoo/UglifyJS2/issues/203
+ // if the code argument is a constant, then we can minify it.
+ try {
+ var code = "n(function(" + self.args.slice(0, -1).map(function(arg) {
+ return arg.value;
+ }).join(",") + "){" + self.args[self.args.length - 1].value + "})";
+ var ast = parse(code);
+ var mangle = { ie8: compressor.option("ie8") };
+ ast.figure_out_scope(mangle);
+ var comp = new Compressor(compressor.options);
+ ast = ast.transform(comp);
+ ast.figure_out_scope(mangle);
+ base54.reset();
+ ast.compute_char_frequency(mangle);
+ ast.mangle_names(mangle);
+ var fun;
+ ast.walk(new TreeWalker(function(node) {
+ if (fun) return true;
+ if (node instanceof AST_Lambda) {
+ fun = node;
+ return true;
+ }
+ }));
+ var code = OutputStream();
+ AST_BlockStatement.prototype._codegen.call(fun, fun, code);
+ self.args = [
+ make_node(AST_String, self, {
+ value: fun.argnames.map(function(arg) {
+ return arg.print_to_string();
+ }).join(",")
+ }),
+ make_node(AST_String, self.args[self.args.length - 1], {
+ value: code.get().replace(/^\{|\}$/g, "")
+ })
+ ];
+ return self;
+ } catch (ex) {
+ if (ex instanceof JS_Parse_Error) {
+ compressor.warn("Error parsing code passed to new Function [{file}:{line},{col}]", self.args[self.args.length - 1].start);
+ compressor.warn(ex.toString());
+ } else {
+ throw ex;
+ }
+ }
+ }
+ }
+ var stat = fn instanceof AST_Function && fn.body[0];
+ if (compressor.option("inline") && stat instanceof AST_Return) {
+ var value = stat.value;
+ if (!value || value.is_constant_expression()) {
+ var args = self.args.concat(value || make_node(AST_Undefined, self));
+ return make_sequence(self, args).optimize(compressor);
+ }
+ }
if (exp instanceof AST_Function) {
- if (exp.body[0] instanceof AST_Return) {
- var value = exp.body[0].value;
- if (!value || value.is_constant()) {
- var args = self.args.concat(value || make_node(AST_Undefined, self));
- return AST_Seq.from_array(args).transform(compressor);
+ if (compressor.option("inline")
+ && !exp.name
+ && !exp.uses_arguments
+ && !exp.uses_eval
+ && exp.body.length == 1
+ && all(exp.argnames, function(arg) {
+ return arg.__unused;
+ })
+ && !self.has_pure_annotation(compressor)) {
+ var value;
+ if (stat instanceof AST_Return) {
+ value = stat.value;
+ } else if (stat instanceof AST_SimpleStatement) {
+ value = make_node(AST_UnaryPrefix, stat, {
+ operator: "void",
+ expression: stat.body
+ });
+ }
+ if (value) {
+ var tw = new TreeWalker(function(node) {
+ if (!value) return true;
+ if (node instanceof AST_SymbolRef) {
+ var ref = node.scope.find_variable(node);
+ if (ref && ref.scope.parent_scope === fn.parent_scope) {
+ value = null;
+ return true;
+ }
+ }
+ if (node instanceof AST_This && !tw.find_parent(AST_Scope)) {
+ value = null;
+ return true;
+ }
+ });
+ value.walk(tw);
+ }
+ if (value) {
+ var args = self.args.concat(value);
+ return make_sequence(self, args).optimize(compressor);
}
}
if (compressor.option("side_effects") && all(exp.body, is_empty)) {
var args = self.args.concat(make_node(AST_Undefined, self));
- return AST_Seq.from_array(args).transform(compressor);
+ return make_sequence(self, args).optimize(compressor);
}
}
if (compressor.option("drop_console")) {
@@ -3070,9 +3399,7 @@ merge(Compressor.prototype, {
while (name.expression) {
name = name.expression;
}
- if (name instanceof AST_SymbolRef
- && name.name == "console"
- && name.undeclared()) {
+ if (is_undeclared_ref(name) && name.name == "console") {
return make_node(AST_Undefined, self).optimize(compressor);
}
}
@@ -3082,13 +3409,18 @@ merge(Compressor.prototype, {
&& is_iife_call(self)) {
return self.negate(compressor, true);
}
+ var ev = self.evaluate(compressor);
+ if (ev !== self) {
+ ev = make_node_from_constant(ev, self).optimize(compressor);
+ return best_of(compressor, ev, self);
+ }
return self;
});
OPT(AST_New, function(self, compressor){
if (compressor.option("unsafe")) {
var exp = self.expression;
- if (exp instanceof AST_SymbolRef && exp.undeclared()) {
+ if (is_undeclared_ref(exp)) {
switch (exp.name) {
case "Object":
case "RegExp":
@@ -3102,73 +3434,118 @@ merge(Compressor.prototype, {
return self;
});
- OPT(AST_Seq, function(self, compressor){
- if (!compressor.option("side_effects"))
+ OPT(AST_Sequence, function(self, compressor){
+ if (!compressor.option("side_effects")) return self;
+ var expressions = [];
+ filter_for_side_effects();
+ var end = expressions.length - 1;
+ trim_right_for_undefined();
+ if (end > 0 && compressor.option("cascade")) trim_left_for_assignment();
+ if (end == 0) {
+ self = maintain_this_binding(compressor.parent(), self, expressions[0]);
+ if (!(self instanceof AST_Sequence)) self = self.optimize(compressor);
return self;
- self.car = self.car.drop_side_effect_free(compressor, first_in_statement(compressor));
- if (!self.car) return maintain_this_binding(compressor.parent(), self, self.cdr);
- if (compressor.option("cascade")) {
- var left;
- if (self.car instanceof AST_Assign
- && !self.car.left.has_side_effects(compressor)) {
- left = self.car.left;
- } else if (self.car instanceof AST_Unary
- && (self.car.operator == "++" || self.car.operator == "--")) {
- left = self.car.expression;
- }
- if (left
- && !(left instanceof AST_SymbolRef
- && (left.definition().orig[0] instanceof AST_SymbolLambda
- || is_reference_const(left)))) {
- var parent, field;
- var cdr = self.cdr;
+ }
+ self.expressions = expressions;
+ return self;
+
+ function filter_for_side_effects() {
+ var first = first_in_statement(compressor);
+ var last = self.expressions.length - 1;
+ self.expressions.forEach(function(expr, index) {
+ if (index < last) expr = expr.drop_side_effect_free(compressor, first);
+ if (expr) {
+ merge_sequence(expressions, expr);
+ first = false;
+ }
+ });
+ }
+
+ function trim_right_for_undefined() {
+ while (end > 0 && is_undefined(expressions[end], compressor)) end--;
+ if (end < expressions.length - 1) {
+ expressions[end] = make_node(AST_UnaryPrefix, self, {
+ operator : "void",
+ expression : expressions[end]
+ });
+ expressions.length = end + 1;
+ }
+ }
+
+ function trim_left_for_assignment() {
+ for (var i = 0, j = 1; j <= end; j++) {
+ var left = expressions[i];
+ var cdr = expressions[j];
+ if (left instanceof AST_Assign
+ && !left.left.has_side_effects(compressor)) {
+ left = left.left;
+ } else if (left instanceof AST_Unary
+ && (left.operator == "++" || left.operator == "--")) {
+ left = left.expression;
+ } else left = null;
+ if (!left || is_lhs_read_only(left)) {
+ expressions[++i] = cdr;
+ continue;
+ }
+ var parent = null, field;
+ expressions[j] = cdr = cdr.clone();
while (true) {
if (cdr.equivalent_to(left)) {
- var car = self.car instanceof AST_UnaryPostfix ? make_node(AST_UnaryPrefix, self.car, {
- operator: self.car.operator,
- expression: left
- }) : self.car;
+ var car = expressions[i];
+ if (car instanceof AST_UnaryPostfix) {
+ car = make_node(AST_UnaryPrefix, car, {
+ operator: car.operator,
+ expression: left
+ });
+ } else {
+ car.write_only = false;
+ }
if (parent) {
parent[field] = car;
- return self.cdr;
+ expressions[i] = expressions[j];
+ } else {
+ expressions[i] = car;
}
- return car;
+ break;
}
if (cdr instanceof AST_Binary && !(cdr instanceof AST_Assign)) {
if (cdr.left.is_constant()) {
- if (cdr.operator == "||" || cdr.operator == "&&") break;
+ if (cdr.operator == "||" || cdr.operator == "&&") {
+ expressions[++i] = expressions[j];
+ break;
+ }
field = "right";
} else {
field = "left";
}
} else if (cdr instanceof AST_Call
+ && !(left instanceof AST_PropAccess && cdr.expression.equivalent_to(left))
+ || cdr instanceof AST_PropAccess
|| cdr instanceof AST_Unary && !unary_side_effects(cdr.operator)) {
field = "expression";
- } else break;
+ } else if (cdr instanceof AST_Conditional) {
+ field = "condition";
+ } else {
+ expressions[++i] = expressions[j];
+ break;
+ }
parent = cdr;
- cdr = cdr[field];
+ cdr = cdr[field] = cdr[field].clone();
}
}
+ end = i;
+ expressions.length = end + 1;
}
- if (is_undefined(self.cdr, compressor)) {
- return make_node(AST_UnaryPrefix, self, {
- operator : "void",
- expression : self.car
- });
- }
- return self;
});
AST_Unary.DEFMETHOD("lift_sequences", function(compressor){
if (compressor.option("sequences")) {
- if (this.expression instanceof AST_Seq) {
- var seq = this.expression;
- var x = seq.to_array();
+ if (this.expression instanceof AST_Sequence) {
+ var x = this.expression.expressions.slice();
var e = this.clone();
e.expression = x.pop();
x.push(e);
- seq = AST_Seq.from_array(x).transform(compressor);
- return seq;
+ return make_sequence(this, x).optimize(compressor);
}
}
return this;
@@ -3186,15 +3563,12 @@ merge(Compressor.prototype, {
|| e instanceof AST_NaN
|| e instanceof AST_Infinity
|| e instanceof AST_Undefined)) {
- if (e instanceof AST_Seq) {
- e = e.to_array();
+ if (e instanceof AST_Sequence) {
+ e = e.expressions.slice();
e.push(make_node(AST_True, self));
- return AST_Seq.from_array(e).optimize(compressor);
+ return make_sequence(self, e).optimize(compressor);
}
- return make_node(AST_Seq, self, {
- car: e,
- cdr: make_node(AST_True, self)
- }).optimize(compressor);
+ return make_sequence(self, [ e, make_node(AST_True, self) ]).optimize(compressor);
}
var seq = self.lift_sequences(compressor);
if (seq !== self) {
@@ -3224,10 +3598,10 @@ merge(Compressor.prototype, {
// typeof always returns a non-empty string, thus it's
// always true in booleans
compressor.warn("Boolean expression always true [{file}:{line},{col}]", self.start);
- return (e instanceof AST_SymbolRef ? make_node(AST_True, self) : make_node(AST_Seq, self, {
- car: e,
- cdr: make_node(AST_True, self)
- })).optimize(compressor);
+ return (e instanceof AST_SymbolRef ? make_node(AST_True, self) : make_sequence(self, [
+ e,
+ make_node(AST_True, self)
+ ])).optimize(compressor);
}
}
if (self.operator == "-" && e instanceof AST_Infinity) {
@@ -3259,29 +3633,32 @@ merge(Compressor.prototype, {
AST_Binary.DEFMETHOD("lift_sequences", function(compressor){
if (compressor.option("sequences")) {
- if (this.left instanceof AST_Seq) {
- var seq = this.left;
- var x = seq.to_array();
+ if (this.left instanceof AST_Sequence) {
+ var x = this.left.expressions.slice();
var e = this.clone();
e.left = x.pop();
x.push(e);
- return AST_Seq.from_array(x).optimize(compressor);
+ return make_sequence(this, x).optimize(compressor);
}
- if (this.right instanceof AST_Seq && !this.left.has_side_effects(compressor)) {
+ if (this.right instanceof AST_Sequence && !this.left.has_side_effects(compressor)) {
var assign = this.operator == "=" && this.left instanceof AST_SymbolRef;
- var root = this.right.clone();
- var cursor, seq = root;
- while (assign || !seq.car.has_side_effects(compressor)) {
- cursor = seq;
- if (seq.cdr instanceof AST_Seq) {
- seq = seq.cdr = seq.cdr.clone();
- } else break;
+ var x = this.right.expressions;
+ var last = x.length - 1;
+ for (var i = 0; i < last; i++) {
+ if (!assign && x[i].has_side_effects(compressor)) break;
}
- if (cursor) {
+ if (i == last) {
+ x = x.slice();
var e = this.clone();
- e.right = cursor.cdr;
- cursor.cdr = e;
- return root.optimize(compressor);
+ e.right = x.pop();
+ x.push(e);
+ return make_sequence(this, x).optimize(compressor);
+ } else if (i > 0) {
+ var e = this.clone();
+ e.right = make_sequence(this.right, x.slice(i));
+ x = x.slice(0, i);
+ x.push(e);
+ return make_sequence(this, x).optimize(compressor);
}
}
}
@@ -3331,13 +3708,14 @@ merge(Compressor.prototype, {
case "==":
case "!=":
// "undefined" == typeof x => undefined === x
- if (self.left instanceof AST_String
+ if (compressor.option("typeofs")
+ && self.left instanceof AST_String
&& self.left.value == "undefined"
&& self.right instanceof AST_UnaryPrefix
&& self.right.operator == "typeof") {
var expr = self.right.expression;
- if (expr instanceof AST_SymbolRef ? !expr.undeclared()
- : !(expr instanceof AST_PropAccess) || compressor.option("screw_ie8")) {
+ if (expr instanceof AST_SymbolRef ? expr.is_declared(compressor)
+ : !(expr instanceof AST_PropAccess && compressor.option("ie8"))) {
self.right = expr;
self.left = make_node(AST_Undefined, self.left).optimize(compressor);
if (self.operator.length == 2) self.operator += "=";
@@ -3350,17 +3728,17 @@ merge(Compressor.prototype, {
var rr = self.right.evaluate(compressor);
if (ll && typeof ll == "string") {
compressor.warn("+ in boolean context always true [{file}:{line},{col}]", self.start);
- return make_node(AST_Seq, self, {
- car: self.right,
- cdr: make_node(AST_True, self)
- }).optimize(compressor);
+ return make_sequence(self, [
+ self.right,
+ make_node(AST_True, self)
+ ]).optimize(compressor);
}
if (rr && typeof rr == "string") {
compressor.warn("+ in boolean context always true [{file}:{line},{col}]", self.start);
- return make_node(AST_Seq, self, {
- car: self.left,
- cdr: make_node(AST_True, self)
- }).optimize(compressor);
+ return make_sequence(self, [
+ self.left,
+ make_node(AST_True, self)
+ ]).optimize(compressor);
}
}
if (compressor.option("comparisons") && self.is_boolean()) {
@@ -3414,10 +3792,10 @@ merge(Compressor.prototype, {
var rr = self.right.evaluate(compressor);
if (!rr) {
compressor.warn("Boolean && always false [{file}:{line},{col}]", self.start);
- return make_node(AST_Seq, self, {
- car: self.left,
- cdr: make_node(AST_False, self)
- }).optimize(compressor);
+ return make_sequence(self, [
+ self.left,
+ make_node(AST_False, self)
+ ]).optimize(compressor);
} else if (rr !== self.right) {
compressor.warn("Dropping side-effect-free && in boolean context [{file}:{line},{col}]", self.start);
return self.left.optimize(compressor);
@@ -3440,10 +3818,10 @@ merge(Compressor.prototype, {
return self.left.optimize(compressor);
} else if (rr !== self.right) {
compressor.warn("Boolean || always true [{file}:{line},{col}]", self.start);
- return make_node(AST_Seq, self, {
- car: self.left,
- cdr: make_node(AST_True, self)
- }).optimize(compressor);
+ return make_sequence(self, [
+ self.left,
+ make_node(AST_True, self)
+ ]).optimize(compressor);
}
}
break;
@@ -3661,8 +4039,8 @@ merge(Compressor.prototype, {
return def.optimize(compressor);
}
// testing against !self.scope.uses_with first is an optimization
- if (compressor.option("screw_ie8")
- && self.undeclared()
+ if (!compressor.option("ie8")
+ && is_undeclared_ref(self)
&& (!self.scope.uses_with || !compressor.find_parent(AST_With))) {
switch (self.name) {
case "undefined":
@@ -3673,17 +4051,27 @@ merge(Compressor.prototype, {
return make_node(AST_Infinity, self).optimize(compressor);
}
}
- if (compressor.option("evaluate")
- && compressor.option("reduce_vars")
+ if (compressor.option("reduce_vars")
&& is_lhs(self, compressor.parent()) !== self) {
var d = self.definition();
var fixed = self.fixed_value();
- if (fixed) {
+ if (fixed instanceof AST_Defun) {
+ d.fixed = fixed = make_node(AST_Function, fixed, fixed);
+ }
+ if (compressor.option("unused")
+ && fixed instanceof AST_Function
+ && d.references.length == 1
+ && !(d.scope.uses_arguments && d.orig[0] instanceof AST_SymbolFunarg)
+ && !d.scope.uses_eval
+ && compressor.find_parent(AST_Scope) === fixed.parent_scope) {
+ return fixed.clone(true);
+ }
+ if (compressor.option("evaluate") && fixed) {
if (d.should_replace === undefined) {
var init = fixed.evaluate(compressor);
if (init !== fixed && (compressor.option("unsafe_regexp") || !(init instanceof RegExp))) {
init = make_node_from_constant(init, fixed);
- var value = init.optimize(compressor).print_to_string().length;
+ var value_length = init.optimize(compressor).print_to_string().length;
var fn;
if (has_symbol_ref(fixed)) {
fn = function() {
@@ -3691,18 +4079,18 @@ merge(Compressor.prototype, {
return result === init ? result.clone(true) : result;
};
} else {
- value = Math.min(value, fixed.print_to_string().length);
+ value_length = Math.min(value_length, fixed.print_to_string().length);
fn = function() {
var result = best_of_expression(init.optimize(compressor), fixed);
return result === init || result === fixed ? result.clone(true) : result;
};
}
- var name = d.name.length;
+ var name_length = d.name.length;
var overhead = 0;
- if (compressor.option("unused") && (!d.global || compressor.option("toplevel"))) {
- overhead = (name + 2 + value) / d.references.length;
+ if (compressor.option("unused") && !compressor.exposed(d)) {
+ overhead = (name_length + 2 + value_length) / d.references.length;
}
- d.should_replace = value <= name + overhead ? fn : false;
+ d.should_replace = value_length <= name_length + overhead ? fn : false;
} else {
d.should_replace = false;
}
@@ -3813,10 +4201,12 @@ merge(Compressor.prototype, {
OPT(AST_Conditional, function(self, compressor){
if (!compressor.option("conditionals")) return self;
- if (self.condition instanceof AST_Seq) {
- var car = self.condition.car;
- self.condition = self.condition.cdr;
- return AST_Seq.cons(car, self);
+ // This looks like lift_sequences(), should probably be under "sequences"
+ if (self.condition instanceof AST_Sequence) {
+ var expressions = self.condition.expressions.slice();
+ self.condition = expressions.pop();
+ expressions.push(self);
+ return make_sequence(self, expressions);
}
var cond = self.condition.evaluate(compressor);
if (cond !== self.condition) {
@@ -3899,10 +4289,10 @@ merge(Compressor.prototype, {
}
// x ? y : y --> x, y
if (consequent.equivalent_to(alternative)) {
- return make_node(AST_Seq, self, {
- car: self.condition,
- cdr: consequent
- }).optimize(compressor);
+ return make_sequence(self, [
+ self.condition,
+ consequent
+ ]).optimize(compressor);
}
if (is_true(self.consequent)) {
@@ -4005,7 +4395,7 @@ merge(Compressor.prototype, {
var prop = self.property;
if (prop instanceof AST_String && compressor.option("properties")) {
prop = prop.getValue();
- if (RESERVED_WORDS(prop) ? compressor.option("screw_ie8") : is_identifier_string(prop)) {
+ if (is_identifier_string(prop)) {
return make_node(AST_Dot, self, {
expression : self.expression,
property : prop
@@ -4026,25 +4416,41 @@ merge(Compressor.prototype, {
return self;
});
+ AST_Lambda.DEFMETHOD("contains_this", function() {
+ var result;
+ var self = this;
+ self.walk(new TreeWalker(function(node) {
+ if (result) return true;
+ if (node instanceof AST_This) return result = true;
+ if (node !== self && node instanceof AST_Scope) return true;
+ }));
+ return result;
+ });
+
OPT(AST_Dot, function(self, compressor){
var def = self.resolve_defines(compressor);
if (def) {
return def.optimize(compressor);
}
- var prop = self.property;
- if (RESERVED_WORDS(prop) && !compressor.option("screw_ie8")) {
- return make_node(AST_Sub, self, {
- expression : self.expression,
- property : make_node(AST_String, self, {
- value: prop
- })
- }).optimize(compressor);
+ if (compressor.option("unsafe") && self.expression instanceof AST_Object) {
+ var values = self.expression.properties;
+ for (var i = values.length; --i >= 0;) {
+ if (values[i].key === self.property) {
+ var value = values[i].value;
+ if (value instanceof AST_Function ? !value.contains_this() : !value.has_side_effects(compressor)) {
+ var obj = self.expression.clone();
+ obj.properties = obj.properties.slice();
+ obj.properties.splice(i, 1);
+ return make_sequence(self, [ obj, value ]).optimize(compressor);
+ }
+ }
+ }
}
if (compressor.option("unsafe_proto")
&& self.expression instanceof AST_Dot
&& self.expression.property == "prototype") {
var exp = self.expression.expression;
- if (exp instanceof AST_SymbolRef && exp.undeclared()) switch (exp.name) {
+ if (is_undeclared_ref(exp)) switch (exp.name) {
case "Array":
self.expression = make_node(AST_Array, self.expression, {
elements: []
@@ -4072,10 +4478,10 @@ merge(Compressor.prototype, {
function literals_in_boolean_context(self, compressor) {
if (compressor.option("booleans") && compressor.in_boolean_context()) {
- return best_of(compressor, self, make_node(AST_Seq, self, {
- car: self,
- cdr: make_node(AST_True, self)
- }).optimize(compressor));
+ return best_of(compressor, self, make_sequence(self, [
+ self,
+ make_node(AST_True, self)
+ ]).optimize(compressor));
}
return self;
};
diff --git a/node_modules/uglify-js/lib/minify.js b/node_modules/uglify-js/lib/minify.js
new file mode 100644
index 000000000..773e953ae
--- /dev/null
+++ b/node_modules/uglify-js/lib/minify.js
@@ -0,0 +1,220 @@
+"use strict";
+
+var to_ascii = typeof atob == "undefined" ? function(b64) {
+ return new Buffer(b64, "base64").toString();
+} : atob;
+var to_base64 = typeof btoa == "undefined" ? function(str) {
+ return new Buffer(str).toString("base64");
+} : btoa;
+
+function read_source_map(code) {
+ var match = /\n\/\/# sourceMappingURL=data:application\/json(;.*?)?;base64,(.*)/.exec(code);
+ if (!match) {
+ AST_Node.warn("inline source map not found");
+ return null;
+ }
+ return to_ascii(match[2]);
+}
+
+function set_shorthand(name, options, keys) {
+ if (options[name]) {
+ keys.forEach(function(key) {
+ if (options[key]) {
+ if (typeof options[key] != "object") options[key] = {};
+ if (!(name in options[key])) options[key][name] = options[name];
+ }
+ });
+ }
+}
+
+function init_cache(cache) {
+ if (!cache) return;
+ if (!("cname" in cache)) cache.cname = -1;
+ if (!("props" in cache)) {
+ cache.props = new Dictionary();
+ } else if (!(cache.props instanceof Dictionary)) {
+ cache.props = Dictionary.fromObject(cache.props);
+ }
+}
+
+function to_json(cache) {
+ return {
+ cname: cache.cname,
+ props: cache.props.toObject()
+ };
+}
+
+function minify(files, options) {
+ var warn_function = AST_Node.warn_function;
+ try {
+ options = defaults(options, {
+ compress: {},
+ ie8: false,
+ keep_fnames: false,
+ mangle: {},
+ nameCache: null,
+ output: {},
+ parse: {},
+ sourceMap: false,
+ timings: false,
+ toplevel: false,
+ warnings: false,
+ wrap: false,
+ }, true);
+ var timings = options.timings && {
+ start: Date.now()
+ };
+ set_shorthand("ie8", options, [ "compress", "mangle", "output" ]);
+ set_shorthand("keep_fnames", options, [ "compress", "mangle" ]);
+ set_shorthand("toplevel", options, [ "compress", "mangle" ]);
+ set_shorthand("warnings", options, [ "compress" ]);
+ var quoted_props;
+ if (options.mangle) {
+ options.mangle = defaults(options.mangle, {
+ cache: options.nameCache && (options.nameCache.vars || {}),
+ eval: false,
+ ie8: false,
+ keep_fnames: false,
+ properties: false,
+ reserved: [],
+ toplevel: false,
+ }, true);
+ if (options.mangle.properties) {
+ if (typeof options.mangle.properties != "object") {
+ options.mangle.properties = {};
+ }
+ if (options.mangle.properties.keep_quoted) {
+ quoted_props = options.mangle.properties.reserved;
+ if (!Array.isArray(quoted_props)) quoted_props = [];
+ options.mangle.properties.reserved = quoted_props;
+ }
+ if (options.nameCache && !("cache" in options.mangle.properties)) {
+ options.mangle.properties.cache = options.nameCache.props || {};
+ }
+ }
+ init_cache(options.mangle.cache);
+ init_cache(options.mangle.properties.cache);
+ }
+ if (options.sourceMap) {
+ options.sourceMap = defaults(options.sourceMap, {
+ content: null,
+ filename: null,
+ includeSources: false,
+ root: null,
+ url: null,
+ }, true);
+ }
+ var warnings = [];
+ if (options.warnings && !AST_Node.warn_function) {
+ AST_Node.warn_function = function(warning) {
+ warnings.push(warning);
+ };
+ }
+ if (timings) timings.parse = Date.now();
+ var toplevel;
+ if (files instanceof AST_Toplevel) {
+ toplevel = files;
+ } else {
+ if (typeof files == "string") {
+ files = [ files ];
+ }
+ options.parse = options.parse || {};
+ options.parse.toplevel = null;
+ for (var name in files) if (HOP(files, name)) {
+ options.parse.filename = name;
+ options.parse.toplevel = parse(files[name], options.parse);
+ if (options.sourceMap && options.sourceMap.content == "inline") {
+ if (Object.keys(files).length > 1)
+ throw new Error("inline source map only works with singular input");
+ options.sourceMap.content = read_source_map(files[name]);
+ }
+ }
+ toplevel = options.parse.toplevel;
+ }
+ if (quoted_props) {
+ reserve_quoted_keys(toplevel, quoted_props);
+ }
+ if (options.wrap) {
+ toplevel = toplevel.wrap_commonjs(options.wrap);
+ }
+ if (timings) timings.scope1 = Date.now();
+ if (options.compress) toplevel.figure_out_scope(options.mangle);
+ if (timings) timings.compress = Date.now();
+ if (options.compress) toplevel = new Compressor(options.compress).compress(toplevel);
+ if (timings) timings.scope2 = Date.now();
+ if (options.mangle) toplevel.figure_out_scope(options.mangle);
+ if (timings) timings.mangle = Date.now();
+ if (options.mangle) {
+ base54.reset();
+ toplevel.compute_char_frequency(options.mangle);
+ toplevel.mangle_names(options.mangle);
+ }
+ if (timings) timings.properties = Date.now();
+ if (options.mangle && options.mangle.properties) {
+ toplevel = mangle_properties(toplevel, options.mangle.properties);
+ }
+ if (timings) timings.output = Date.now();
+ var result = {};
+ if (options.output.ast) {
+ result.ast = toplevel;
+ }
+ if (!HOP(options.output, "code") || options.output.code) {
+ if (options.sourceMap) {
+ if (typeof options.sourceMap.content == "string") {
+ options.sourceMap.content = JSON.parse(options.sourceMap.content);
+ }
+ options.output.source_map = SourceMap({
+ file: options.sourceMap.filename,
+ orig: options.sourceMap.content,
+ root: options.sourceMap.root
+ });
+ if (options.sourceMap.includeSources) {
+ if (files instanceof AST_Toplevel) {
+ throw new Error("original source content unavailable");
+ } else for (var name in files) if (HOP(files, name)) {
+ options.output.source_map.get().setSourceContent(name, files[name]);
+ }
+ }
+ }
+ delete options.output.ast;
+ delete options.output.code;
+ var stream = OutputStream(options.output);
+ toplevel.print(stream);
+ result.code = stream.get();
+ if (options.sourceMap) {
+ result.map = options.output.source_map.toString();
+ if (options.sourceMap.url == "inline") {
+ result.code += "\n//# sourceMappingURL=data:application/json;charset=utf-8;base64," + to_base64(result.map);
+ } else if (options.sourceMap.url) {
+ result.code += "\n//# sourceMappingURL=" + options.sourceMap.url;
+ }
+ }
+ }
+ if (options.nameCache && options.mangle) {
+ if (options.mangle.cache) options.nameCache.vars = to_json(options.mangle.cache);
+ if (options.mangle.properties && options.mangle.properties.cache) {
+ options.nameCache.props = to_json(options.mangle.properties.cache);
+ }
+ }
+ if (timings) {
+ timings.end = Date.now();
+ result.timings = {
+ parse: 1e-3 * (timings.scope1 - timings.parse),
+ scope: 1e-3 * (timings.compress - timings.scope1 + timings.mangle - timings.scope2),
+ compress: 1e-3 * (timings.scope2 - timings.compress),
+ mangle: 1e-3 * (timings.properties - timings.mangle),
+ properties: 1e-3 * (timings.output - timings.properties),
+ output: 1e-3 * (timings.end - timings.output),
+ total: 1e-3 * (timings.end - timings.start)
+ }
+ }
+ if (warnings.length) {
+ result.warnings = warnings;
+ }
+ return result;
+ } catch (ex) {
+ return { error: ex };
+ } finally {
+ AST_Node.warn_function = warn_function;
+ }
+}
diff --git a/node_modules/uglify-js/lib/mozilla-ast.js b/node_modules/uglify-js/lib/mozilla-ast.js
index 88a2eb59f..8d7ee4b85 100644
--- a/node_modules/uglify-js/lib/mozilla-ast.js
+++ b/node_modules/uglify-js/lib/mozilla-ast.js
@@ -145,7 +145,11 @@
});
},
SequenceExpression: function(M) {
- return AST_Seq.from_array(M.expressions.map(from_moz));
+ return new AST_Sequence({
+ start : my_start_token(M),
+ end : my_end_token(M),
+ expressions: M.expressions.map(from_moz)
+ });
},
MemberExpression: function(M) {
return new (M.computed ? AST_Sub : AST_Dot)({
@@ -164,7 +168,7 @@
});
},
VariableDeclaration: function(M) {
- return new (M.kind === "const" ? AST_Const : AST_Var)({
+ return new AST_Var({
start : my_start_token(M),
end : my_end_token(M),
definitions : M.declarations.map(from_moz)
@@ -200,7 +204,7 @@
Identifier: function(M) {
var p = FROM_MOZ_STACK[FROM_MOZ_STACK.length - 2];
return new ( p.type == "LabeledStatement" ? AST_Label
- : p.type == "VariableDeclarator" && p.id === M ? (p.kind == "const" ? AST_SymbolConst : AST_SymbolVar)
+ : p.type == "VariableDeclarator" && p.id === M ? AST_SymbolVar
: p.type == "FunctionExpression" ? (p.id === M ? AST_SymbolLambda : AST_SymbolFunarg)
: p.type == "FunctionDeclaration" ? (p.id === M ? AST_SymbolDefun : AST_SymbolFunarg)
: p.type == "CatchClause" ? AST_SymbolCatch
@@ -320,15 +324,15 @@
def_to_moz(AST_Definitions, function To_Moz_VariableDeclaration(M) {
return {
type: "VariableDeclaration",
- kind: M instanceof AST_Const ? "const" : "var",
+ kind: "var",
declarations: M.definitions.map(to_moz)
};
});
- def_to_moz(AST_Seq, function To_Moz_SequenceExpression(M) {
+ def_to_moz(AST_Sequence, function To_Moz_SequenceExpression(M) {
return {
type: "SequenceExpression",
- expressions: M.to_array().map(to_moz)
+ expressions: M.expressions.map(to_moz)
};
});
diff --git a/node_modules/uglify-js/lib/output.js b/node_modules/uglify-js/lib/output.js
index 0731fb492..4c873f10d 100644
--- a/node_modules/uglify-js/lib/output.js
+++ b/node_modules/uglify-js/lib/output.js
@@ -57,6 +57,7 @@ function OutputStream(options) {
beautify : false,
bracketize : false,
comments : false,
+ ie8 : false,
indent_level : 4,
indent_start : 0,
inline_script : true,
@@ -66,12 +67,10 @@ function OutputStream(options) {
preserve_line : false,
quote_keys : false,
quote_style : 0,
- screw_ie8 : true,
semicolons : true,
shebang : true,
source_map : null,
- space_colon : true,
- unescape_regexps : false,
+ webkit : false,
width : 80,
wrap_iife : false,
}, true);
@@ -110,7 +109,7 @@ function OutputStream(options) {
var current_pos = 0;
var OUTPUT = "";
- function to_ascii(str, identifier) {
+ var to_utf8 = options.ascii_only ? function(str, identifier) {
return str.replace(/[\u0000-\u001f\u007f-\uffff]/g, function(ch) {
var code = ch.charCodeAt(0).toString(16);
if (code.length <= 2 && !identifier) {
@@ -121,6 +120,12 @@ function OutputStream(options) {
return "\\u" + code;
}
});
+ } : function(str) {
+ return str.replace(/[\ud800-\udbff](?![\udc00-\udfff])/g, function(ch) {
+ return "\\u" + ch.charCodeAt(0).toString(16);
+ }).replace(/(^|[^\ud800-\udbff])([\udc00-\udfff])/g, function(match, prefix, ch) {
+ return prefix + "\\u" + ch.charCodeAt(0).toString(16);
+ });
};
function make_string(str, quote) {
@@ -136,7 +141,7 @@ function OutputStream(options) {
case "\t": return "\\t";
case "\b": return "\\b";
case "\f": return "\\f";
- case "\x0B": return options.screw_ie8 ? "\\v" : "\\x0B";
+ case "\x0B": return options.ie8 ? "\\x0B" : "\\v";
case "\u2028": return "\\u2028";
case "\u2029": return "\\u2029";
case "\ufeff": return "\\ufeff";
@@ -151,7 +156,7 @@ function OutputStream(options) {
function quote_double() {
return '"' + str.replace(/\x22/g, '\\"') + '"';
}
- if (options.ascii_only) str = to_ascii(str);
+ str = to_utf8(str);
switch (options.quote_style) {
case 1:
return quote_single();
@@ -176,8 +181,7 @@ function OutputStream(options) {
function make_name(name) {
name = name.toString();
- if (options.ascii_only)
- name = to_ascii(name, true);
+ name = to_utf8(name, true);
return name;
};
@@ -191,12 +195,43 @@ function OutputStream(options) {
var might_need_semicolon = false;
var might_add_newline = 0;
var last = "";
+ var mapping_token, mapping_name, mappings = options.source_map && [];
+
+ var do_add_mapping = mappings ? function() {
+ mappings.forEach(function(mapping) {
+ try {
+ options.source_map.add(
+ mapping.token.file,
+ mapping.line, mapping.col,
+ mapping.token.line, mapping.token.col,
+ !mapping.name && mapping.token.type == "name" ? mapping.token.value : mapping.name
+ );
+ } catch(ex) {
+ AST_Node.warn("Couldn't figure out mapping for {file}:{line},{col} → {cline},{ccol} [{name}]", {
+ file: mapping.token.file,
+ line: mapping.token.line,
+ col: mapping.token.col,
+ cline: mapping.line,
+ ccol: mapping.col,
+ name: mapping.name || ""
+ })
+ }
+ });
+ mappings = [];
+ } : noop;
var ensure_line_len = options.max_line_len ? function() {
if (current_col > options.max_line_len) {
if (might_add_newline) {
var left = OUTPUT.slice(0, might_add_newline);
var right = OUTPUT.slice(might_add_newline);
+ if (mappings) {
+ var delta = right.length - current_col;
+ mappings.forEach(function(mapping) {
+ mapping.line++;
+ mapping.col += delta;
+ });
+ }
OUTPUT = left + "\n" + right;
current_line++;
current_pos++;
@@ -206,7 +241,10 @@ function OutputStream(options) {
AST_Node.warn("Output exceeds {max_line_len} characters", options);
}
}
- might_add_newline = 0;
+ if (might_add_newline) {
+ might_add_newline = 0;
+ do_add_mapping();
+ }
} : noop;
var requireSemicolonChars = makePredicate("( [ + * / - , .");
@@ -266,6 +304,18 @@ function OutputStream(options) {
}
might_need_space = false;
}
+
+ if (mapping_token) {
+ mappings.push({
+ token: mapping_token,
+ name: mapping_name,
+ line: current_line,
+ col: current_col
+ });
+ mapping_token = false;
+ if (!might_add_newline) do_add_mapping();
+ }
+
OUTPUT += str;
current_pos += str.length;
var a = str.split(/\r?\n/), n = a.length - 1;
@@ -357,27 +407,12 @@ function OutputStream(options) {
function colon() {
print(":");
- if (options.space_colon) space();
+ space();
};
- var add_mapping = options.source_map ? function(token, name) {
- try {
- if (token) options.source_map.add(
- token.file || "?",
- current_line, current_col,
- token.line, token.col,
- (!name && token.type == "name") ? token.value : name
- );
- } catch(ex) {
- AST_Node.warn("Couldn't figure out mapping for {file}:{line},{col} → {cline},{ccol} [{name}]", {
- file: token.file,
- line: token.line,
- col: token.col,
- cline: current_line,
- ccol: current_col,
- name: name || ""
- })
- }
+ var add_mapping = mappings ? function(token, name) {
+ mapping_token = token;
+ mapping_name = name;
} : noop;
function get() {
@@ -403,7 +438,7 @@ function OutputStream(options) {
last : function() { return last },
semicolon : semicolon,
force_semicolon : force_semicolon,
- to_ascii : to_ascii,
+ to_utf8 : to_utf8,
print_name : function(name) { print(make_name(name)) },
print_string : function(str, quote, escape_directive) {
var encoded = encode_string(str, quote);
@@ -471,6 +506,7 @@ function OutputStream(options) {
use_asm = prev_use_asm;
}
});
+ AST_Node.DEFMETHOD("_print", AST_Node.prototype.print);
AST_Node.DEFMETHOD("print_to_string", function(options){
var s = OutputStream(options);
@@ -568,6 +604,13 @@ function OutputStream(options) {
return true;
}
+ if (output.option('webkit')) {
+ var p = output.parent();
+ if (p instanceof AST_PropAccess && p.expression === this) {
+ return true;
+ }
+ }
+
if (output.option('wrap_iife')) {
var p = output.parent();
return p instanceof AST_Call && p.expression === this;
@@ -588,7 +631,7 @@ function OutputStream(options) {
|| p instanceof AST_Call && p.expression === this;
});
- PARENS(AST_Seq, function(output){
+ PARENS(AST_Sequence, function(output){
var p = output.parent();
return p instanceof AST_Call // (foo, bar)() or foo(1, (2, 3), 4)
|| p instanceof AST_Unary // !(foo, bar, baz)
@@ -634,14 +677,15 @@ function OutputStream(options) {
// parens around it too, otherwise the call will be
// interpreted as passing the arguments to the upper New
// expression.
- try {
- this.walk(new TreeWalker(function(node){
- if (node instanceof AST_Call) throw p;
- }));
- } catch(ex) {
- if (ex !== p) throw ex;
- return true;
- }
+ var parens = false;
+ this.walk(new TreeWalker(function(node) {
+ if (parens || node instanceof AST_Scope) return true;
+ if (node instanceof AST_Call) {
+ parens = true;
+ return true;
+ }
+ }));
+ return parens;
}
});
@@ -677,7 +721,7 @@ function OutputStream(options) {
}
});
- PARENS([ AST_Assign, AST_Conditional ], function (output){
+ PARENS([ AST_Assign, AST_Conditional ], function(output){
var p = output.parent();
// !(a = false) → true
if (p instanceof AST_Unary)
@@ -902,7 +946,7 @@ function OutputStream(options) {
function make_then(self, output) {
var b = self.body;
if (output.option("bracketize")
- || !output.option("screw_ie8") && b instanceof AST_Do)
+ || output.option("ie8") && b instanceof AST_Do)
return make_block(b, output);
// The squeezer replaces "block"-s that contain only a single
// statement with the statement itself; technically, the AST
@@ -1033,24 +1077,19 @@ function OutputStream(options) {
DEFPRINT(AST_Var, function(self, output){
self._do_print(output, "var");
});
- DEFPRINT(AST_Const, function(self, output){
- self._do_print(output, "const");
- });
function parenthesize_for_noin(node, output, noin) {
- if (!noin) node.print(output);
- else try {
- // need to take some precautions here:
- // https://github.com/mishoo/UglifyJS2/issues/60
- node.walk(new TreeWalker(function(node){
- if (node instanceof AST_Binary && node.operator == "in")
- throw output;
- }));
- node.print(output);
- } catch(ex) {
- if (ex !== output) throw ex;
- node.print(output, true);
- }
+ var parens = false;
+ // need to take some precautions here:
+ // https://github.com/mishoo/UglifyJS2/issues/60
+ if (noin) node.walk(new TreeWalker(function(node) {
+ if (parens || node instanceof AST_Scope) return true;
+ if (node instanceof AST_Binary && node.operator == "in") {
+ parens = true;
+ return true;
+ }
+ }));
+ node.print(output, parens);
};
DEFPRINT(AST_VarDef, function(self, output){
@@ -1070,6 +1109,9 @@ function OutputStream(options) {
self.expression.print(output);
if (self instanceof AST_New && !need_constructor_parens(self, output))
return;
+ if (self.expression instanceof AST_Lambda) {
+ output.add_mapping(self.start);
+ }
output.with_parens(function(){
self.args.forEach(function(expr, i){
if (i) output.comma();
@@ -1083,18 +1125,19 @@ function OutputStream(options) {
AST_Call.prototype._codegen(self, output);
});
- AST_Seq.DEFMETHOD("_do_print", function(output){
- this.car.print(output);
- if (this.cdr) {
- output.comma();
- if (output.should_break()) {
- output.newline();
- output.indent();
+ AST_Sequence.DEFMETHOD("_do_print", function(output){
+ this.expressions.forEach(function(node, index) {
+ if (index > 0) {
+ output.comma();
+ if (output.should_break()) {
+ output.newline();
+ output.indent();
+ }
}
- this.cdr.print(output);
- }
+ node.print(output);
+ });
});
- DEFPRINT(AST_Seq, function(self, output){
+ DEFPRINT(AST_Sequence, function(self, output){
self._do_print(output);
// var p = output.parent();
// if (p instanceof AST_Statement) {
@@ -1108,15 +1151,23 @@ function OutputStream(options) {
DEFPRINT(AST_Dot, function(self, output){
var expr = self.expression;
expr.print(output);
- if (expr instanceof AST_Number && expr.getValue() >= 0) {
- if (!/[xa-f.)]/i.test(output.last())) {
- output.print(".");
+ var prop = self.property;
+ if (output.option("ie8") && RESERVED_WORDS(prop)) {
+ output.print("[");
+ output.add_mapping(self.end);
+ output.print_string(prop);
+ output.print("]");
+ } else {
+ if (expr instanceof AST_Number && expr.getValue() >= 0) {
+ if (!/[xa-f.)]/i.test(output.last())) {
+ output.print(".");
+ }
}
+ output.print(".");
+ // the name after dot would be mapped about here.
+ output.add_mapping(self.end);
+ output.print_name(prop);
}
- output.print(".");
- // the name after dot would be mapped about here.
- output.add_mapping(self.end);
- output.print_name(self.property);
});
DEFPRINT(AST_Sub, function(self, output){
self.expression.print(output);
@@ -1207,9 +1258,8 @@ function OutputStream(options) {
});
else output.print("{}");
});
- DEFPRINT(AST_ObjectKeyVal, function(self, output){
- var key = self.key;
- var quote = self.quote;
+
+ function print_property_name(key, quote, output) {
if (output.option("quote_keys")) {
output.print_string(key + "");
} else if ((typeof key == "number"
@@ -1217,7 +1267,7 @@ function OutputStream(options) {
&& +key + "" == key)
&& parseFloat(key) >= 0) {
output.print(make_num(key));
- } else if (RESERVED_WORDS(key) ? output.option("screw_ie8") : is_identifier_string(key)) {
+ } else if (RESERVED_WORDS(key) ? !output.option("ie8") : is_identifier_string(key)) {
if (quote && output.option("keep_quoted_props")) {
output.print_string(key, quote);
} else {
@@ -1226,20 +1276,24 @@ function OutputStream(options) {
} else {
output.print_string(key, quote);
}
+ }
+
+ DEFPRINT(AST_ObjectKeyVal, function(self, output){
+ print_property_name(self.key, self.quote, output);
output.colon();
self.value.print(output);
});
- DEFPRINT(AST_ObjectSetter, function(self, output){
- output.print("set");
+ AST_ObjectProperty.DEFMETHOD("_print_getter_setter", function(type, output) {
+ output.print(type);
output.space();
- self.key.print(output);
- self.value._do_print(output, true);
+ print_property_name(this.key.name, this.quote, output);
+ this.value._do_print(output, true);
+ });
+ DEFPRINT(AST_ObjectSetter, function(self, output){
+ self._print_getter_setter("set", output);
});
DEFPRINT(AST_ObjectGetter, function(self, output){
- output.print("get");
- output.space();
- self.key.print(output);
- self.value._do_print(output, true);
+ self._print_getter_setter("get", output);
});
DEFPRINT(AST_Symbol, function(self, output){
var def = self.definition();
@@ -1263,46 +1317,13 @@ function OutputStream(options) {
}
});
- function regexp_safe_literal(code) {
- return [
- 0x5c , // \
- 0x2f , // /
- 0x2e , // .
- 0x2b , // +
- 0x2a , // *
- 0x3f , // ?
- 0x28 , // (
- 0x29 , // )
- 0x5b , // [
- 0x5d , // ]
- 0x7b , // {
- 0x7d , // }
- 0x24 , // $
- 0x5e , // ^
- 0x3a , // :
- 0x7c , // |
- 0x21 , // !
- 0x0a , // \n
- 0x0d , // \r
- 0x00 , // \0
- 0xfeff , // Unicode BOM
- 0x2028 , // unicode "line separator"
- 0x2029 , // unicode "paragraph separator"
- ].indexOf(code) < 0;
- };
-
DEFPRINT(AST_RegExp, function(self, output){
- var str = self.getValue().toString();
- if (output.option("ascii_only")) {
- str = output.to_ascii(str);
- } else if (output.option("unescape_regexps")) {
- str = str.split("\\\\").map(function(str){
- return str.replace(/\\u[0-9a-fA-F]{4}|\\x[0-9a-fA-F]{2}/g, function(s){
- var code = parseInt(s.substr(2), 16);
- return regexp_safe_literal(code) ? String.fromCharCode(code) : s;
- });
- }).join("\\\\");
+ var regexp = self.getValue();
+ var str = regexp.toString();
+ if (regexp.raw_source) {
+ str = "/" + regexp.raw_source + str.slice(str.lastIndexOf("/"));
}
+ str = output.to_utf8(str);
output.print(str);
var p = output.parent();
if (p instanceof AST_Binary && /^in/.test(p.operator) && p.left === self)
diff --git a/node_modules/uglify-js/lib/parse.js b/node_modules/uglify-js/lib/parse.js
index 014822ad9..e2dd04b6c 100644
--- a/node_modules/uglify-js/lib/parse.js
+++ b/node_modules/uglify-js/lib/parse.js
@@ -115,8 +115,6 @@ var PUNC_BEFORE_EXPRESSION = makePredicate(characters("[{(,;:"));
var PUNC_CHARS = makePredicate(characters("[]{}(),;:"));
-var REGEXP_MODIFIERS = makePredicate(characters("gmsiy"));
-
/* -----[ Tokenizer ]----- */
// regexps adapted from http://xregexp.com/plugins/#unicode
@@ -477,31 +475,33 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
return name;
};
- var read_regexp = with_eof_error("Unterminated regular expression", function(regexp){
+ var read_regexp = with_eof_error("Unterminated regular expression", function(source) {
var prev_backslash = false, ch, in_class = false;
while ((ch = next(true))) if (NEWLINE_CHARS(ch)) {
parse_error("Unexpected line terminator");
} else if (prev_backslash) {
- regexp += "\\" + ch;
+ source += "\\" + ch;
prev_backslash = false;
} else if (ch == "[") {
in_class = true;
- regexp += ch;
+ source += ch;
} else if (ch == "]" && in_class) {
in_class = false;
- regexp += ch;
+ source += ch;
} else if (ch == "/" && !in_class) {
break;
} else if (ch == "\\") {
prev_backslash = true;
} else {
- regexp += ch;
+ source += ch;
}
var mods = read_name();
try {
- return token("regexp", new RegExp(regexp, mods));
+ var regexp = new RegExp(source, mods);
+ regexp.raw_source = source;
+ return token("regexp", regexp);
} catch(e) {
- parse_error(e.message);
+ parse_error(e.message);
}
});
@@ -633,8 +633,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
}
next_token.has_directive = function(directive) {
- return S.directives[directive] !== undefined &&
- S.directives[directive] > 0;
+ return S.directives[directive] > 0;
}
return next_token;
@@ -683,9 +682,7 @@ var PRECEDENCE = (function(a, ret){
{}
);
-var STATEMENTS_WITH_LABELS = array_to_hash([ "for", "do", "while", "switch" ]);
-
-var ATOMIC_START_TOKEN = array_to_hash([ "atom", "num", "string", "regexp", "name" ]);
+var ATOMIC_START_TOKEN = makePredicate([ "atom", "num", "string", "regexp", "name" ]);
/* -----[ Parser ]----- */
@@ -693,14 +690,13 @@ function parse($TEXT, options) {
options = defaults(options, {
bare_returns : false,
- cli : false,
expression : false,
filename : null,
html5_comments : true,
shebang : true,
strict : false,
toplevel : null,
- });
+ }, true);
var S = {
input : (typeof $TEXT == "string"
@@ -941,12 +937,6 @@ function parse($TEXT, options) {
semicolon();
return node;
- case "const":
- next();
- var node = const_();
- semicolon();
- return node;
-
case "with":
if (S.input.has_directive("use strict")) {
croak("Strict mode may not include a with statement");
@@ -1020,8 +1010,12 @@ function parse($TEXT, options) {
? (next(), var_(true))
: expression(true, true);
if (is("operator", "in")) {
- if (init instanceof AST_Var && init.definitions.length > 1)
- croak("Only one variable declaration allowed in for..in loop");
+ if (init instanceof AST_Var) {
+ if (init.definitions.length > 1)
+ croak("Only one variable declaration allowed in for..in loop", init.start.line, init.start.col, init.start.pos);
+ } else if (!is_assignable(init)) {
+ croak("Invalid left-hand side in for..in loop", init.start.line, init.start.col, init.start.pos);
+ }
next();
return for_in(init);
}
@@ -1061,29 +1055,32 @@ function parse($TEXT, options) {
if (in_statement && !name)
unexpected();
expect("(");
+ var argnames = [];
+ for (var first = true; !is("punc", ")");) {
+ if (first) first = false; else expect(",");
+ argnames.push(as_symbol(AST_SymbolFunarg));
+ }
+ next();
+ var loop = S.in_loop;
+ var labels = S.labels;
+ ++S.in_function;
+ S.in_directives = true;
+ S.input.push_directives_stack();
+ S.in_loop = 0;
+ S.labels = [];
+ var body = block_();
+ if (S.input.has_directive("use strict")) {
+ if (name) strict_verify_symbol(name);
+ argnames.forEach(strict_verify_symbol);
+ }
+ S.input.pop_directives_stack();
+ --S.in_function;
+ S.in_loop = loop;
+ S.labels = labels;
return new ctor({
name: name,
- argnames: (function(first, a){
- while (!is("punc", ")")) {
- if (first) first = false; else expect(",");
- a.push(as_symbol(AST_SymbolFunarg));
- }
- next();
- return a;
- })(true, []),
- body: (function(loop, labels){
- ++S.in_function;
- S.in_directives = true;
- S.input.push_directives_stack();
- S.in_loop = 0;
- S.labels = [];
- var a = block_();
- S.input.pop_directives_stack();
- --S.in_function;
- S.in_loop = loop;
- S.labels = labels;
- return a;
- })(S.in_loop, S.labels)
+ argnames: argnames,
+ body: body
});
};
@@ -1179,12 +1176,12 @@ function parse($TEXT, options) {
});
};
- function vardefs(no_in, in_const) {
+ function vardefs(no_in) {
var a = [];
for (;;) {
a.push(new AST_VarDef({
start : S.token,
- name : as_symbol(in_const ? AST_SymbolConst : AST_SymbolVar),
+ name : as_symbol(AST_SymbolVar),
value : is("operator", "=") ? (next(), expression(false, no_in)) : null,
end : prev()
}));
@@ -1198,15 +1195,7 @@ function parse($TEXT, options) {
var var_ = function(no_in) {
return new AST_Var({
start : prev(),
- definitions : vardefs(no_in, false),
- end : prev()
- });
- };
-
- var const_ = function() {
- return new AST_Const({
- start : prev(),
- definitions : vardefs(false, true),
+ definitions : vardefs(no_in),
end : prev()
});
};
@@ -1233,7 +1222,6 @@ function parse($TEXT, options) {
var tok = S.token, ret;
switch (tok.type) {
case "name":
- case "keyword":
ret = _make_symbol(AST_SymbolRef);
break;
case "num":
@@ -1263,13 +1251,6 @@ function parse($TEXT, options) {
break;
}
break;
- case "operator":
- if (!is_identifier_string(tok.value)) {
- croak("Invalid getter/setter name: " + tok.value,
- tok.line, tok.col, tok.pos);
- }
- ret = _make_symbol(AST_SymbolRef);
- break;
}
next();
return ret;
@@ -1303,7 +1284,7 @@ function parse($TEXT, options) {
func.end = prev();
return subscripts(func, allow_calls);
}
- if (ATOMIC_START_TOKEN[S.token.type]) {
+ if (ATOMIC_START_TOKEN(S.token.type)) {
return subscripts(as_atom_node(), allow_calls);
}
unexpected();
@@ -1417,12 +1398,20 @@ function parse($TEXT, options) {
});
};
+ function strict_verify_symbol(sym) {
+ if (sym.name == "arguments" || sym.name == "eval")
+ croak("Unexpected " + sym.name + " in strict mode", sym.start.line, sym.start.col, sym.start.pos);
+ }
+
function as_symbol(type, noerror) {
if (!is("name")) {
if (!noerror) croak("Name expected");
return null;
}
var sym = _make_symbol(type);
+ if (S.input.has_directive("use strict") && sym instanceof AST_SymbolDeclaration) {
+ strict_verify_symbol(sym);
+ }
next();
return sym;
};
@@ -1483,8 +1472,17 @@ function parse($TEXT, options) {
function make_unary(ctor, token, expr) {
var op = token.value;
- if ((op == "++" || op == "--") && !is_assignable(expr))
- croak("Invalid use of " + op + " operator", token.line, token.col, token.pos);
+ switch (op) {
+ case "++":
+ case "--":
+ if (!is_assignable(expr))
+ croak("Invalid use of " + op + " operator", token.line, token.col, token.pos);
+ break;
+ case "delete":
+ if (expr instanceof AST_SymbolRef && S.input.has_directive("use strict"))
+ croak("Calling delete on expression not allowed in strict mode", expr.start.line, expr.start.col, expr.start.pos);
+ break;
+ }
return new ctor({ operator: op, expression: expr });
};
@@ -1529,7 +1527,6 @@ function parse($TEXT, options) {
};
function is_assignable(expr) {
- if (options.cli) return true;
return expr instanceof AST_PropAccess || expr instanceof AST_SymbolRef;
};
@@ -1554,17 +1551,18 @@ function parse($TEXT, options) {
var expression = function(commas, no_in) {
var start = S.token;
- var expr = maybe_assign(no_in);
- if (commas && is("punc", ",")) {
+ var exprs = [];
+ while (true) {
+ exprs.push(maybe_assign(no_in));
+ if (!commas || !is("punc", ",")) break;
next();
- return new AST_Seq({
- start : start,
- car : expr,
- cdr : expression(true, no_in),
- end : peek()
- });
+ commas = true;
}
- return expr;
+ return exprs.length == 1 ? exprs[0] : new AST_Sequence({
+ start : start,
+ expressions : exprs,
+ end : peek()
+ });
};
function in_loop(cont) {
diff --git a/node_modules/uglify-js/lib/propmangle.js b/node_modules/uglify-js/lib/propmangle.js
index b62229903..36a67e80a 100644
--- a/node_modules/uglify-js/lib/propmangle.js
+++ b/node_modules/uglify-js/lib/propmangle.js
@@ -43,16 +43,16 @@
"use strict";
-function find_builtins() {
+function find_builtins(reserved) {
// NaN will be included due to Number.NaN
- var a = [
+ [
"null",
"true",
"false",
"Infinity",
"-Infinity",
"undefined",
- ];
+ ].forEach(add);
[ Object, Array, Function, Number,
String, Boolean, Error, Math,
Date, RegExp
@@ -63,24 +63,52 @@ function find_builtins() {
}
});
function add(name) {
- push_uniq(a, name);
+ push_uniq(reserved, name);
}
- return a;
+}
+
+function reserve_quoted_keys(ast, reserved) {
+ function add(name) {
+ push_uniq(reserved, name);
+ }
+
+ ast.walk(new TreeWalker(function(node) {
+ if (node instanceof AST_ObjectKeyVal && node.quote) {
+ add(node.key);
+ } else if (node instanceof AST_Sub) {
+ addStrings(node.property, add);
+ }
+ }));
+}
+
+function addStrings(node, add) {
+ node.walk(new TreeWalker(function(node) {
+ if (node instanceof AST_Sequence) {
+ addStrings(node.expressions[node.expressions.length - 1], add);
+ } else if (node instanceof AST_String) {
+ add(node.value);
+ } else if (node instanceof AST_Conditional) {
+ addStrings(node.consequent, add);
+ addStrings(node.alternative, add);
+ }
+ return true;
+ }));
}
function mangle_properties(ast, options) {
options = defaults(options, {
+ builtins: false,
cache: null,
debug: false,
- ignore_quoted: false,
+ keep_quoted: false,
only_cache: false,
regex: null,
reserved: null,
- });
+ }, true);
var reserved = options.reserved;
- if (reserved == null)
- reserved = find_builtins();
+ if (!Array.isArray(reserved)) reserved = [];
+ if (!options.builtins) find_builtins(reserved);
var cache = options.cache;
if (cache == null) {
@@ -91,12 +119,11 @@ function mangle_properties(ast, options) {
}
var regex = options.regex;
- var ignore_quoted = options.ignore_quoted;
// note debug is either false (disabled), or a string of the debug suffix to use (enabled).
// note debug may be enabled as an empty string, which is falsey. Also treat passing 'true'
// the same as passing an empty string.
- var debug = (options.debug !== false);
+ var debug = options.debug !== false;
var debug_name_suffix;
if (debug) {
debug_name_suffix = (options.debug === true ? "" : options.debug);
@@ -104,12 +131,11 @@ function mangle_properties(ast, options) {
var names_to_mangle = [];
var unmangleable = [];
- var ignored = {};
// step 1: find candidates to mangle
ast.walk(new TreeWalker(function(node){
if (node instanceof AST_ObjectKeyVal) {
- add(node.key, ignore_quoted && node.quote);
+ add(node.key);
}
else if (node instanceof AST_ObjectProperty) {
// setter or getter, since KeyVal is handled above
@@ -119,15 +145,14 @@ function mangle_properties(ast, options) {
add(node.property);
}
else if (node instanceof AST_Sub) {
- addStrings(node.property, ignore_quoted);
+ addStrings(node.property, add);
}
}));
// step 2: transform the tree, renaming properties
return ast.transform(new TreeTransformer(function(node){
if (node instanceof AST_ObjectKeyVal) {
- if (!(ignore_quoted && node.quote))
- node.key = mangle(node.key);
+ node.key = mangle(node.key);
}
else if (node instanceof AST_ObjectProperty) {
// setter or getter
@@ -136,22 +161,9 @@ function mangle_properties(ast, options) {
else if (node instanceof AST_Dot) {
node.property = mangle(node.property);
}
- else if (node instanceof AST_Sub) {
- if (!ignore_quoted)
- node.property = mangleStrings(node.property);
+ else if (!options.keep_quoted && node instanceof AST_Sub) {
+ node.property = mangleStrings(node.property);
}
- // else if (node instanceof AST_String) {
- // if (should_mangle(node.value)) {
- // AST_Node.warn(
- // "Found \"{prop}\" property candidate for mangling in an arbitrary string [{file}:{line},{col}]", {
- // file : node.start.file,
- // line : node.start.line,
- // col : node.start.col,
- // prop : node.value
- // }
- // );
- // }
- // }
}));
// only function declarations after this line
@@ -167,19 +179,13 @@ function mangle_properties(ast, options) {
}
function should_mangle(name) {
- if (ignore_quoted && name in ignored) return false;
if (regex && !regex.test(name)) return false;
if (reserved.indexOf(name) >= 0) return false;
return cache.props.has(name)
|| names_to_mangle.indexOf(name) >= 0;
}
- function add(name, ignore) {
- if (ignore) {
- ignored[name] = true;
- return;
- }
-
+ function add(name) {
if (can_mangle(name))
push_uniq(names_to_mangle, name);
@@ -199,19 +205,16 @@ function mangle_properties(ast, options) {
// debug mode: use a prefix and suffix to preserve readability, e.g. o.foo -> o._$foo$NNN_.
var debug_mangled = "_$" + name + "$" + debug_name_suffix + "_";
- if (can_mangle(debug_mangled) && !(ignore_quoted && debug_mangled in ignored)) {
+ if (can_mangle(debug_mangled)) {
mangled = debug_mangled;
}
}
// either debug mode is off, or it is on and we could not use the mangled name
if (!mangled) {
- // note can_mangle() does not check if the name collides with the 'ignored' set
- // (filled with quoted properties when ignore_quoted set). Make sure we add this
- // check so we don't collide with a quoted name.
do {
mangled = base54(++cache.cname);
- } while (!can_mangle(mangled) || (ignore_quoted && mangled in ignored));
+ } while (!can_mangle(mangled));
}
cache.props.set(name, mangled);
@@ -219,36 +222,11 @@ function mangle_properties(ast, options) {
return mangled;
}
- function addStrings(node, ignore) {
- var out = {};
- try {
- (function walk(node){
- node.walk(new TreeWalker(function(node){
- if (node instanceof AST_Seq) {
- walk(node.cdr);
- return true;
- }
- if (node instanceof AST_String) {
- add(node.value, ignore);
- return true;
- }
- if (node instanceof AST_Conditional) {
- walk(node.consequent);
- walk(node.alternative);
- return true;
- }
- throw out;
- }));
- })(node);
- } catch(ex) {
- if (ex !== out) throw ex;
- }
- }
-
function mangleStrings(node) {
return node.transform(new TreeTransformer(function(node){
- if (node instanceof AST_Seq) {
- node.cdr = mangleStrings(node.cdr);
+ if (node instanceof AST_Sequence) {
+ var last = node.expressions.length - 1;
+ node.expressions[last] = mangleStrings(node.expressions[last]);
}
else if (node instanceof AST_String) {
node.value = mangle(node.value);
@@ -260,5 +238,4 @@ function mangle_properties(ast, options) {
return node;
}));
}
-
}
diff --git a/node_modules/uglify-js/lib/scope.js b/node_modules/uglify-js/lib/scope.js
index bf6dbcbbf..df7b2076c 100644
--- a/node_modules/uglify-js/lib/scope.js
+++ b/node_modules/uglify-js/lib/scope.js
@@ -76,10 +76,10 @@ SymbolDef.prototype = {
else if (!this.mangled_name && !this.unmangleable(options)) {
var s = this.scope;
var sym = this.orig[0];
- if (!options.screw_ie8 && sym instanceof AST_SymbolLambda)
+ if (options.ie8 && sym instanceof AST_SymbolLambda)
s = s.parent_scope;
var def;
- if (this.defun && (def = this.defun.variables.get(this.name))) {
+ if (def = this.redefined()) {
this.mangled_name = def.mangled_name || def.name;
} else
this.mangled_name = s.next_mangled(options, this);
@@ -87,13 +87,16 @@ SymbolDef.prototype = {
cache.set(this.name, this.mangled_name);
}
}
+ },
+ redefined: function() {
+ return this.defun && this.defun.variables.get(this.name);
}
};
AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
options = defaults(options, {
cache: null,
- screw_ie8: true,
+ ie8: false,
});
// pass 1: setup scope chaining and handle definitions
@@ -156,8 +159,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
// later.
(node.scope = defun.parent_scope).def_function(node);
}
- else if (node instanceof AST_SymbolVar
- || node instanceof AST_SymbolConst) {
+ else if (node instanceof AST_SymbolVar) {
defun.def_variable(node);
if (defun !== scope) {
node.mark_enclosed(options);
@@ -184,16 +186,8 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
self.walk(tw);
// pass 2: find back references and eval
- var func = null;
- var globals = self.globals = new Dictionary();
+ self.globals = new Dictionary();
var tw = new TreeWalker(function(node, descend){
- if (node instanceof AST_Lambda) {
- var prev_func = func;
- func = node;
- descend();
- func = prev_func;
- return true;
- }
if (node instanceof AST_LoopControl && node.label) {
node.label.thedef.references.push(node);
return true;
@@ -206,21 +200,30 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
}
}
var sym = node.scope.find_variable(name);
- if (node.scope instanceof AST_Lambda && name == "arguments") {
- node.scope.uses_arguments = true;
- }
if (!sym) {
sym = self.def_global(node);
+ } else if (sym.scope instanceof AST_Lambda && name == "arguments") {
+ sym.scope.uses_arguments = true;
}
node.thedef = sym;
node.reference(options);
return true;
}
+ // ensure mangling works if catch reuses a scope variable
+ var def;
+ if (node instanceof AST_SymbolCatch && (def = node.definition().redefined())) {
+ var s = node.scope;
+ while (s) {
+ push_uniq(s.enclosed, def);
+ if (s === def.scope) break;
+ s = s.parent_scope;
+ }
+ }
});
self.walk(tw);
// pass 3: fix up any scoping issue with IE8
- if (!options.screw_ie8) {
+ if (options.ie8) {
self.walk(new TreeWalker(function(node, descend) {
if (node instanceof AST_SymbolCatch) {
var name = node.name;
@@ -268,7 +271,7 @@ AST_Scope.DEFMETHOD("init_scope_vars", function(parent_scope){
AST_Lambda.DEFMETHOD("init_scope_vars", function(){
AST_Scope.prototype.init_scope_vars.apply(this, arguments);
this.uses_arguments = false;
- this.def_variable(new AST_SymbolVar({
+ this.def_variable(new AST_SymbolFunarg({
name: "arguments",
start: this.start,
end: this.end
@@ -325,8 +328,8 @@ AST_Scope.DEFMETHOD("next_mangled", function(options){
if (!is_identifier(m)) continue; // skip over "do"
// https://github.com/mishoo/UglifyJS2/issues/242 -- do not
- // shadow a name excepted from mangling.
- if (options.except.indexOf(m) >= 0) continue;
+ // shadow a name reserved from mangling.
+ if (options.reserved.indexOf(m) >= 0) continue;
// we must ensure that the mangled name does not shadow a name
// from some parent scope that is referenced in this or in
@@ -358,31 +361,18 @@ AST_Function.DEFMETHOD("next_mangled", function(options, def){
});
AST_Symbol.DEFMETHOD("unmangleable", function(options){
- return this.definition().unmangleable(options);
+ var def = this.definition();
+ return !def || def.unmangleable(options);
});
// labels are always mangleable
-AST_Label.DEFMETHOD("unmangleable", function(){
- return false;
-});
+AST_Label.DEFMETHOD("unmangleable", return_false);
AST_Symbol.DEFMETHOD("unreferenced", function(){
return this.definition().references.length == 0
&& !(this.scope.uses_eval || this.scope.uses_with);
});
-AST_Symbol.DEFMETHOD("undeclared", function(){
- return this.definition().undeclared;
-});
-
-AST_LabelRef.DEFMETHOD("undeclared", function(){
- return false;
-});
-
-AST_Label.DEFMETHOD("undeclared", function(){
- return false;
-});
-
AST_Symbol.DEFMETHOD("definition", function(){
return this.thedef;
});
@@ -392,21 +382,22 @@ AST_Symbol.DEFMETHOD("global", function(){
});
AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){
- return defaults(options, {
+ options = defaults(options, {
eval : false,
- except : [],
+ ie8 : false,
keep_fnames : false,
- screw_ie8 : true,
- sort : false, // Ignored. Flag retained for backwards compatibility.
+ reserved : [],
toplevel : false,
});
+ if (!Array.isArray(options.reserved)) options.reserved = [];
+ return options;
});
AST_Toplevel.DEFMETHOD("mangle_names", function(options){
options = this._default_mangler_options(options);
// Never mangle arguments
- options.except.push('arguments');
+ options.reserved.push('arguments');
// We only need to mangle declaration nodes. Special logic wired
// into the code generator will display the mangled name if it's
@@ -417,7 +408,7 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
if (options.cache) {
this.globals.each(function(symbol){
- if (options.except.indexOf(symbol.name) < 0) {
+ if (options.reserved.indexOf(symbol.name) < 0) {
to_mangle.push(symbol);
}
});
@@ -434,7 +425,7 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
if (node instanceof AST_Scope) {
var p = tw.parent(), a = [];
node.variables.each(function(symbol){
- if (options.except.indexOf(symbol.name) < 0) {
+ if (options.reserved.indexOf(symbol.name) < 0) {
a.push(symbol);
}
});
@@ -447,7 +438,7 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
node.mangled_name = name;
return true;
}
- if (options.screw_ie8 && node instanceof AST_SymbolCatch) {
+ if (!options.ie8 && node instanceof AST_SymbolCatch) {
to_mangle.push(node.definition());
return;
}
@@ -462,105 +453,69 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options){
options = this._default_mangler_options(options);
- var tw = new TreeWalker(function(node){
- if (node instanceof AST_Constant)
- base54.consider(node.print_to_string());
- else if (node instanceof AST_Return)
- base54.consider("return");
- else if (node instanceof AST_Throw)
- base54.consider("throw");
- else if (node instanceof AST_Continue)
- base54.consider("continue");
- else if (node instanceof AST_Break)
- base54.consider("break");
- else if (node instanceof AST_Debugger)
- base54.consider("debugger");
- else if (node instanceof AST_Directive)
- base54.consider(node.value);
- else if (node instanceof AST_While)
- base54.consider("while");
- else if (node instanceof AST_Do)
- base54.consider("do while");
- else if (node instanceof AST_If) {
- base54.consider("if");
- if (node.alternative) base54.consider("else");
- }
- else if (node instanceof AST_Var)
- base54.consider("var");
- else if (node instanceof AST_Const)
- base54.consider("const");
- else if (node instanceof AST_Lambda)
- base54.consider("function");
- else if (node instanceof AST_For)
- base54.consider("for");
- else if (node instanceof AST_ForIn)
- base54.consider("for in");
- else if (node instanceof AST_Switch)
- base54.consider("switch");
- else if (node instanceof AST_Case)
- base54.consider("case");
- else if (node instanceof AST_Default)
- base54.consider("default");
- else if (node instanceof AST_With)
- base54.consider("with");
- else if (node instanceof AST_ObjectSetter)
- base54.consider("set" + node.key);
- else if (node instanceof AST_ObjectGetter)
- base54.consider("get" + node.key);
- else if (node instanceof AST_ObjectKeyVal)
- base54.consider(node.key);
- else if (node instanceof AST_New)
- base54.consider("new");
- else if (node instanceof AST_This)
- base54.consider("this");
- else if (node instanceof AST_Try)
- base54.consider("try");
- else if (node instanceof AST_Catch)
- base54.consider("catch");
- else if (node instanceof AST_Finally)
- base54.consider("finally");
- else if (node instanceof AST_Symbol && node.unmangleable(options))
- base54.consider(node.name);
- else if (node instanceof AST_Unary || node instanceof AST_Binary)
- base54.consider(node.operator);
- else if (node instanceof AST_Dot)
- base54.consider(node.property);
- });
- this.walk(tw);
+ try {
+ AST_Node.prototype.print = function(stream, force_parens) {
+ this._print(stream, force_parens);
+ if (this instanceof AST_Symbol && !this.unmangleable(options)) {
+ base54.consider(this.name, -1);
+ } else if (options.properties) {
+ if (this instanceof AST_Dot) {
+ base54.consider(this.property, -1);
+ } else if (this instanceof AST_Sub) {
+ skip_string(this.property);
+ }
+ }
+ };
+ base54.consider(this.print_to_string(), 1);
+ } finally {
+ AST_Node.prototype.print = AST_Node.prototype._print;
+ }
base54.sort();
+
+ function skip_string(node) {
+ if (node instanceof AST_String) {
+ base54.consider(node.value, -1);
+ } else if (node instanceof AST_Conditional) {
+ skip_string(node.consequent);
+ skip_string(node.alternative);
+ } else if (node instanceof AST_Sequence) {
+ skip_string(node.expressions[node.expressions.length - 1]);
+ }
+ }
});
var base54 = (function() {
- var string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_0123456789";
+ var leading = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_".split("");
+ var digits = "0123456789".split("");
var chars, frequency;
function reset() {
frequency = Object.create(null);
- chars = string.split("").map(function(ch){ return ch.charCodeAt(0) });
- chars.forEach(function(ch){ frequency[ch] = 0 });
+ leading.forEach(function(ch) {
+ frequency[ch] = 0;
+ });
+ digits.forEach(function(ch) {
+ frequency[ch] = 0;
+ });
}
- base54.consider = function(str){
+ base54.consider = function(str, delta) {
for (var i = str.length; --i >= 0;) {
- var code = str.charCodeAt(i);
- if (code in frequency) ++frequency[code];
+ frequency[str[i]] += delta;
}
};
+ function compare(a, b) {
+ return frequency[b] - frequency[a];
+ }
base54.sort = function() {
- chars = mergeSort(chars, function(a, b){
- if (is_digit(a) && !is_digit(b)) return 1;
- if (is_digit(b) && !is_digit(a)) return -1;
- return frequency[b] - frequency[a];
- });
+ chars = mergeSort(leading, compare).concat(mergeSort(digits, compare));
};
base54.reset = reset;
reset();
- base54.get = function(){ return chars };
- base54.freq = function(){ return frequency };
function base54(num) {
var ret = "", base = 54;
num++;
do {
num--;
- ret += String.fromCharCode(chars[num % base]);
+ ret += chars[num % base];
num = Math.floor(num / base);
base = 64;
} while (num > 0);
@@ -568,89 +523,3 @@ var base54 = (function() {
};
return base54;
})();
-
-AST_Toplevel.DEFMETHOD("scope_warnings", function(options){
- options = defaults(options, {
- assign_to_global : true,
- eval : true,
- func_arguments : true,
- nested_defuns : true,
- undeclared : false, // this makes a lot of noise
- unreferenced : true,
- });
- var tw = new TreeWalker(function(node){
- if (options.undeclared
- && node instanceof AST_SymbolRef
- && node.undeclared())
- {
- // XXX: this also warns about JS standard names,
- // i.e. Object, Array, parseInt etc. Should add a list of
- // exceptions.
- AST_Node.warn("Undeclared symbol: {name} [{file}:{line},{col}]", {
- name: node.name,
- file: node.start.file,
- line: node.start.line,
- col: node.start.col
- });
- }
- if (options.assign_to_global)
- {
- var sym = null;
- if (node instanceof AST_Assign && node.left instanceof AST_SymbolRef)
- sym = node.left;
- else if (node instanceof AST_ForIn && node.init instanceof AST_SymbolRef)
- sym = node.init;
- if (sym
- && (sym.undeclared()
- || (sym.global() && sym.scope !== sym.definition().scope))) {
- AST_Node.warn("{msg}: {name} [{file}:{line},{col}]", {
- msg: sym.undeclared() ? "Accidental global?" : "Assignment to global",
- name: sym.name,
- file: sym.start.file,
- line: sym.start.line,
- col: sym.start.col
- });
- }
- }
- if (options.eval
- && node instanceof AST_SymbolRef
- && node.undeclared()
- && node.name == "eval") {
- AST_Node.warn("Eval is used [{file}:{line},{col}]", node.start);
- }
- if (options.unreferenced
- && (node instanceof AST_SymbolDeclaration || node instanceof AST_Label)
- && !(node instanceof AST_SymbolCatch)
- && node.unreferenced()) {
- AST_Node.warn("{type} {name} is declared but not referenced [{file}:{line},{col}]", {
- type: node instanceof AST_Label ? "Label" : "Symbol",
- name: node.name,
- file: node.start.file,
- line: node.start.line,
- col: node.start.col
- });
- }
- if (options.func_arguments
- && node instanceof AST_Lambda
- && node.uses_arguments) {
- AST_Node.warn("arguments used in function {name} [{file}:{line},{col}]", {
- name: node.name ? node.name.name : "anonymous",
- file: node.start.file,
- line: node.start.line,
- col: node.start.col
- });
- }
- if (options.nested_defuns
- && node instanceof AST_Defun
- && !(tw.parent() instanceof AST_Scope)) {
- AST_Node.warn("Function {name} declared in nested statement \"{type}\" [{file}:{line},{col}]", {
- name: node.name.name,
- type: tw.parent().TYPE,
- file: node.start.file,
- line: node.start.line,
- col: node.start.col
- });
- }
- });
- this.walk(tw);
-});
diff --git a/node_modules/uglify-js/lib/transform.js b/node_modules/uglify-js/lib/transform.js
index 3018e8ff0..8008e5714 100644
--- a/node_modules/uglify-js/lib/transform.js
+++ b/node_modules/uglify-js/lib/transform.js
@@ -70,7 +70,7 @@ TreeTransformer.prototype = new TreeWalker;
if (y !== undefined) x = y;
}
}
- tw.pop(this);
+ tw.pop();
return x;
});
};
@@ -174,9 +174,8 @@ TreeTransformer.prototype = new TreeWalker;
self.args = do_list(self.args, tw);
});
- _(AST_Seq, function(self, tw){
- self.car = self.car.transform(tw);
- self.cdr = self.cdr.transform(tw);
+ _(AST_Sequence, function(self, tw){
+ self.expressions = do_list(self.expressions, tw);
});
_(AST_Dot, function(self, tw){
diff --git a/node_modules/uglify-js/lib/utils.js b/node_modules/uglify-js/lib/utils.js
index fdb204719..76306919a 100644
--- a/node_modules/uglify-js/lib/utils.js
+++ b/node_modules/uglify-js/lib/utils.js
@@ -43,13 +43,6 @@
"use strict";
-function array_to_hash(a) {
- var ret = Object.create(null);
- for (var i = 0; i < a.length; ++i)
- ret[a[i]] = true;
- return ret;
-};
-
function slice(a, start) {
return Array.prototype.slice.call(a, start || 0);
};
@@ -346,7 +339,7 @@ function first_in_statement(stack) {
for (var i = 0, p; p = stack.parent(i); i++) {
if (p instanceof AST_Statement && p.body === node)
return true;
- if ((p instanceof AST_Seq && p.car === node ) ||
+ if ((p instanceof AST_Sequence && p.expressions[0] === node) ||
(p instanceof AST_Call && p.expression === node && !(p instanceof AST_New) ) ||
(p instanceof AST_Dot && p.expression === node ) ||
(p instanceof AST_Sub && p.expression === node ) ||