aboutsummaryrefslogtreecommitdiff
path: root/node_modules/uglify-js/lib/compress.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/uglify-js/lib/compress.js')
-rw-r--r--node_modules/uglify-js/lib/compress.js2552
1 files changed, 1479 insertions, 1073 deletions
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;
};