diff options
Diffstat (limited to 'node_modules/uglify-js/lib/compress.js')
-rw-r--r-- | node_modules/uglify-js/lib/compress.js | 2552 |
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; }; |