Index: node_modules/vulcanize/node_modules/uglify-js/lib/compress.js |
diff --git a/node_modules/vulcanize/node_modules/uglify-js/lib/compress.js b/node_modules/vulcanize/node_modules/uglify-js/lib/compress.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..e63ce14814075e45f181a7ae118b669cd773bd78 |
--- /dev/null |
+++ b/node_modules/vulcanize/node_modules/uglify-js/lib/compress.js |
@@ -0,0 +1,2409 @@ |
+/*********************************************************************** |
+ |
+ A JavaScript tokenizer / parser / beautifier / compressor. |
+ https://github.com/mishoo/UglifyJS2 |
+ |
+ -------------------------------- (C) --------------------------------- |
+ |
+ Author: Mihai Bazon |
+ <mihai.bazon@gmail.com> |
+ http://mihai.bazon.net/blog |
+ |
+ Distributed under the BSD license: |
+ |
+ Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com> |
+ |
+ Redistribution and use in source and binary forms, with or without |
+ modification, are permitted provided that the following conditions |
+ are met: |
+ |
+ * Redistributions of source code must retain the above |
+ copyright notice, this list of conditions and the following |
+ disclaimer. |
+ |
+ * Redistributions in binary form must reproduce the above |
+ copyright notice, this list of conditions and the following |
+ disclaimer in the documentation and/or other materials |
+ provided with the distribution. |
+ |
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY |
+ EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
+ PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE |
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, |
+ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR |
+ TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF |
+ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
+ SUCH DAMAGE. |
+ |
+ ***********************************************************************/ |
+ |
+"use strict"; |
+ |
+function Compressor(options, false_by_default) { |
+ if (!(this instanceof Compressor)) |
+ return new Compressor(options, false_by_default); |
+ TreeTransformer.call(this, this.before, this.after); |
+ this.options = defaults(options, { |
+ sequences : !false_by_default, |
+ properties : !false_by_default, |
+ dead_code : !false_by_default, |
+ drop_debugger : !false_by_default, |
+ unsafe : false, |
+ unsafe_comps : false, |
+ conditionals : !false_by_default, |
+ comparisons : !false_by_default, |
+ evaluate : !false_by_default, |
+ booleans : !false_by_default, |
+ loops : !false_by_default, |
+ unused : !false_by_default, |
+ hoist_funs : !false_by_default, |
+ keep_fargs : false, |
+ hoist_vars : false, |
+ if_return : !false_by_default, |
+ join_vars : !false_by_default, |
+ cascade : !false_by_default, |
+ side_effects : !false_by_default, |
+ pure_getters : false, |
+ pure_funcs : null, |
+ negate_iife : !false_by_default, |
+ screw_ie8 : false, |
+ drop_console : false, |
+ angular : false, |
+ |
+ warnings : true, |
+ global_defs : {} |
+ }, true); |
+}; |
+ |
+Compressor.prototype = new TreeTransformer; |
+merge(Compressor.prototype, { |
+ option: function(key) { return this.options[key] }, |
+ warn: function() { |
+ if (this.options.warnings) |
+ AST_Node.warn.apply(AST_Node, arguments); |
+ }, |
+ before: function(node, descend, in_list) { |
+ if (node._squeezed) return node; |
+ var was_scope = false; |
+ if (node instanceof AST_Scope) { |
+ node = node.hoist_declarations(this); |
+ was_scope = true; |
+ } |
+ descend(node, this); |
+ node = node.optimize(this); |
+ if (was_scope && node instanceof AST_Scope) { |
+ node.drop_unused(this); |
+ descend(node, this); |
+ } |
+ node._squeezed = true; |
+ return node; |
+ } |
+}); |
+ |
+(function(){ |
+ |
+ function OPT(node, optimizer) { |
+ node.DEFMETHOD("optimize", function(compressor){ |
+ var self = this; |
+ if (self._optimized) return self; |
+ var opt = optimizer(self, compressor); |
+ opt._optimized = true; |
+ if (opt === self) return opt; |
+ return opt.transform(compressor); |
+ }); |
+ }; |
+ |
+ OPT(AST_Node, function(self, compressor){ |
+ return self; |
+ }); |
+ |
+ AST_Node.DEFMETHOD("equivalent_to", function(node){ |
+ // XXX: this is a rather expensive way to test two node's equivalence: |
+ return this.print_to_string() == node.print_to_string(); |
+ }); |
+ |
+ function make_node(ctor, orig, props) { |
+ if (!props) props = {}; |
+ if (orig) { |
+ if (!props.start) props.start = orig.start; |
+ if (!props.end) props.end = orig.end; |
+ } |
+ return new ctor(props); |
+ }; |
+ |
+ function make_node_from_constant(compressor, val, orig) { |
+ // XXX: WIP. |
+ // if (val instanceof AST_Node) return val.transform(new TreeTransformer(null, function(node){ |
+ // if (node instanceof AST_SymbolRef) { |
+ // var scope = compressor.find_parent(AST_Scope); |
+ // var def = scope.find_variable(node); |
+ // node.thedef = def; |
+ // return node; |
+ // } |
+ // })).transform(compressor); |
+ |
+ if (val instanceof AST_Node) return val.transform(compressor); |
+ switch (typeof val) { |
+ case "string": |
+ return make_node(AST_String, orig, { |
+ value: val |
+ }).optimize(compressor); |
+ case "number": |
+ return make_node(isNaN(val) ? AST_NaN : AST_Number, orig, { |
+ value: val |
+ }).optimize(compressor); |
+ case "boolean": |
+ return make_node(val ? AST_True : AST_False, orig).optimize(compressor); |
+ case "undefined": |
+ return make_node(AST_Undefined, orig).optimize(compressor); |
+ default: |
+ if (val === null) { |
+ return make_node(AST_Null, orig).optimize(compressor); |
+ } |
+ if (val instanceof RegExp) { |
+ return make_node(AST_RegExp, orig).optimize(compressor); |
+ } |
+ throw new Error(string_template("Can't handle constant of type: {type}", { |
+ type: typeof val |
+ })); |
+ } |
+ }; |
+ |
+ function as_statement_array(thing) { |
+ if (thing === null) return []; |
+ if (thing instanceof AST_BlockStatement) return thing.body; |
+ if (thing instanceof AST_EmptyStatement) return []; |
+ if (thing instanceof AST_Statement) return [ thing ]; |
+ throw new Error("Can't convert thing to statement array"); |
+ }; |
+ |
+ function is_empty(thing) { |
+ if (thing === null) return true; |
+ if (thing instanceof AST_EmptyStatement) return true; |
+ if (thing instanceof AST_BlockStatement) return thing.body.length == 0; |
+ return false; |
+ }; |
+ |
+ function loop_body(x) { |
+ if (x instanceof AST_Switch) return x; |
+ if (x instanceof AST_For || x instanceof AST_ForIn || x instanceof AST_DWLoop) { |
+ return (x.body instanceof AST_BlockStatement ? x.body : x); |
+ } |
+ return x; |
+ }; |
+ |
+ function tighten_body(statements, compressor) { |
+ var CHANGED; |
+ do { |
+ CHANGED = false; |
+ if (compressor.option("angular")) { |
+ statements = process_for_angular(statements); |
+ } |
+ statements = eliminate_spurious_blocks(statements); |
+ if (compressor.option("dead_code")) { |
+ statements = eliminate_dead_code(statements, compressor); |
+ } |
+ if (compressor.option("if_return")) { |
+ statements = handle_if_return(statements, compressor); |
+ } |
+ if (compressor.option("sequences")) { |
+ statements = sequencesize(statements, compressor); |
+ } |
+ if (compressor.option("join_vars")) { |
+ statements = join_consecutive_vars(statements, compressor); |
+ } |
+ } while (CHANGED); |
+ |
+ if (compressor.option("negate_iife")) { |
+ negate_iifes(statements, compressor); |
+ } |
+ |
+ return statements; |
+ |
+ function process_for_angular(statements) { |
+ 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_node(AST_Array, func, { |
+ elements: func.argnames.map(function(sym){ |
+ return make_node(AST_String, sym, { value: sym.name }); |
+ }) |
+ }) |
+ }) |
+ }); |
+ } |
+ return statements.reduce(function(a, stat){ |
+ a.push(stat); |
+ var token = stat.start; |
+ var comments = token.comments_before; |
+ if (comments && comments.length > 0) { |
+ var last = comments.pop(); |
+ if (/@ngInject/.test(last.value)) { |
+ // 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); |
+ } |
+ } |
+ } |
+ return a; |
+ }, []); |
+ } |
+ |
+ function eliminate_spurious_blocks(statements) { |
+ var seen_dirs = []; |
+ return statements.reduce(function(a, stat){ |
+ if (stat instanceof AST_BlockStatement) { |
+ CHANGED = true; |
+ a.push.apply(a, eliminate_spurious_blocks(stat.body)); |
+ } else if (stat instanceof AST_EmptyStatement) { |
+ CHANGED = true; |
+ } else if (stat instanceof AST_Directive) { |
+ if (seen_dirs.indexOf(stat.value) < 0) { |
+ a.push(stat); |
+ seen_dirs.push(stat.value); |
+ } else { |
+ CHANGED = true; |
+ } |
+ } else { |
+ a.push(stat); |
+ } |
+ return a; |
+ }, []); |
+ }; |
+ |
+ function handle_if_return(statements, compressor) { |
+ var self = compressor.self(); |
+ var in_lambda = self instanceof AST_Lambda; |
+ var ret = []; |
+ loop: 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 ((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: make_node(AST_Undefined, stat) |
+ }); |
+ 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); |
+ stat.body = make_node(AST_BlockStatement, stat, { |
+ body: as_statement_array(stat.alternative).concat(ret) |
+ }); |
+ stat.alternative = null; |
+ ret = [ stat.transform(compressor) ]; |
+ continue loop; |
+ } |
+ //--- |
+ if (ret.length == 1 && in_lambda && ret[0] instanceof AST_SimpleStatement |
+ && (!stat.alternative || stat.alternative instanceof AST_SimpleStatement)) { |
+ CHANGED = true; |
+ ret.push(make_node(AST_Return, ret[0], { |
+ value: make_node(AST_Undefined, ret[0]) |
+ }).transform(compressor)); |
+ ret = as_statement_array(stat.alternative).concat(ret); |
+ ret.unshift(stat); |
+ continue loop; |
+ } |
+ } |
+ |
+ var ab = aborts(stat.body); |
+ var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab.label) : 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 (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); |
+ stat.body = make_node(AST_BlockStatement, stat, { |
+ body: as_statement_array(stat.alternative).concat(ret) |
+ }); |
+ stat.alternative = make_node(AST_BlockStatement, stat, { |
+ body: body |
+ }); |
+ ret = [ stat.transform(compressor) ]; |
+ continue loop; |
+ } |
+ |
+ var ab = aborts(stat.alternative); |
+ var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab.label) : 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 (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) |
+ }); |
+ stat.alternative = make_node(AST_BlockStatement, stat.alternative, { |
+ body: as_statement_array(stat.alternative).slice(0, -1) |
+ }); |
+ ret = [ stat.transform(compressor) ]; |
+ continue loop; |
+ } |
+ |
+ ret.unshift(stat); |
+ break; |
+ default: |
+ ret.unshift(stat); |
+ break; |
+ } |
+ } |
+ return ret; |
+ }; |
+ |
+ function eliminate_dead_code(statements, compressor) { |
+ var has_quit = false; |
+ var orig = statements.length; |
+ 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.label); |
+ if ((stat instanceof AST_Break |
+ && lct instanceof AST_BlockStatement |
+ && 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); |
+ } |
+ } else { |
+ a.push(stat); |
+ } |
+ if (aborts(stat)) has_quit = true; |
+ } |
+ return a; |
+ }, []); |
+ CHANGED = statements.length != orig; |
+ return statements; |
+ }; |
+ |
+ function sequencesize(statements, compressor) { |
+ if (statements.length < 2) return statements; |
+ var seq = [], ret = []; |
+ function push_seq() { |
+ seq = AST_Seq.from_array(seq); |
+ if (seq) ret.push(make_node(AST_SimpleStatement, seq, { |
+ body: seq |
+ })); |
+ seq = []; |
+ }; |
+ statements.forEach(function(stat){ |
+ if (stat instanceof AST_SimpleStatement) seq.push(stat.body); |
+ else push_seq(), ret.push(stat); |
+ }); |
+ push_seq(); |
+ ret = sequencesize_2(ret, compressor); |
+ CHANGED = ret.length != statements.length; |
+ return ret; |
+ }; |
+ |
+ function sequencesize_2(statements, compressor) { |
+ function cons_seq(right) { |
+ ret.pop(); |
+ var left = prev.body; |
+ if (left instanceof AST_Seq) { |
+ left.add(right); |
+ } else { |
+ left = AST_Seq.cons(left, right); |
+ } |
+ return left.transform(compressor); |
+ }; |
+ var ret = [], prev = null; |
+ statements.forEach(function(stat){ |
+ 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); |
+ } |
+ else if (!stat.init) { |
+ stat.init = prev.body; |
+ ret.pop(); |
+ } |
+ } catch(ex) { |
+ if (ex !== opera) throw ex; |
+ } |
+ } |
+ else if (stat instanceof AST_If) { |
+ stat.condition = cons_seq(stat.condition); |
+ } |
+ else if (stat instanceof AST_With) { |
+ stat.expression = cons_seq(stat.expression); |
+ } |
+ else if (stat instanceof AST_Exit && stat.value) { |
+ stat.value = cons_seq(stat.value); |
+ } |
+ else if (stat instanceof AST_Exit) { |
+ stat.value = cons_seq(make_node(AST_Undefined, stat)); |
+ } |
+ else if (stat instanceof AST_Switch) { |
+ stat.expression = cons_seq(stat.expression); |
+ } |
+ } |
+ ret.push(stat); |
+ prev = stat instanceof AST_SimpleStatement ? stat : null; |
+ }); |
+ return ret; |
+ }; |
+ |
+ function join_consecutive_vars(statements, compressor) { |
+ var prev = null; |
+ return statements.reduce(function(a, stat){ |
+ if (stat instanceof AST_Definitions && prev && prev.TYPE == stat.TYPE) { |
+ prev.definitions = prev.definitions.concat(stat.definitions); |
+ CHANGED = true; |
+ } |
+ else if (stat instanceof AST_For |
+ && prev instanceof AST_Definitions |
+ && (!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; |
+ } |
+ else { |
+ prev = stat; |
+ a.push(stat); |
+ } |
+ return a; |
+ }, []); |
+ }; |
+ |
+ function negate_iifes(statements, compressor) { |
+ statements.forEach(function(stat){ |
+ if (stat instanceof AST_SimpleStatement) { |
+ stat.body = (function transform(thing) { |
+ return thing.transform(new TreeTransformer(function(node){ |
+ if (node instanceof AST_Call && node.expression instanceof AST_Function) { |
+ return make_node(AST_UnaryPrefix, node, { |
+ operator: "!", |
+ expression: node |
+ }); |
+ } |
+ else if (node instanceof AST_Call) { |
+ node.expression = transform(node.expression); |
+ } |
+ else if (node instanceof AST_Seq) { |
+ node.car = transform(node.car); |
+ } |
+ else if (node instanceof AST_Conditional) { |
+ var expr = transform(node.condition); |
+ if (expr !== node.condition) { |
+ // it has been negated, reverse |
+ node.condition = expr; |
+ var tmp = node.consequent; |
+ node.consequent = node.alternative; |
+ node.alternative = tmp; |
+ } |
+ } |
+ return node; |
+ })); |
+ })(stat.body); |
+ } |
+ }); |
+ }; |
+ |
+ }; |
+ |
+ function extract_declarations_from_unreachable_code(compressor, stat, target) { |
+ compressor.warn("Dropping unreachable code [{file}:{line},{col}]", stat.start); |
+ stat.walk(new TreeWalker(function(node){ |
+ if (node instanceof AST_Definitions) { |
+ compressor.warn("Declarations in unreachable code! [{file}:{line},{col}]", node.start); |
+ node.remove_initializers(); |
+ target.push(node); |
+ return true; |
+ } |
+ if (node instanceof AST_Defun) { |
+ target.push(node); |
+ return true; |
+ } |
+ if (node instanceof AST_Scope) { |
+ return true; |
+ } |
+ })); |
+ }; |
+ |
+ /* -----[ boolean/negation helpers ]----- */ |
+ |
+ // methods to determine whether an expression has a boolean result type |
+ (function (def){ |
+ var unary_bool = [ "!", "delete" ]; |
+ var binary_bool = [ "in", "instanceof", "==", "!=", "===", "!==", "<", "<=", ">=", ">" ]; |
+ def(AST_Node, function(){ return false }); |
+ def(AST_UnaryPrefix, function(){ |
+ return member(this.operator, unary_bool); |
+ }); |
+ def(AST_Binary, function(){ |
+ return member(this.operator, binary_bool) || |
+ ( (this.operator == "&&" || this.operator == "||") && |
+ this.left.is_boolean() && this.right.is_boolean() ); |
+ }); |
+ def(AST_Conditional, function(){ |
+ return this.consequent.is_boolean() && this.alternative.is_boolean(); |
+ }); |
+ def(AST_Assign, function(){ |
+ return this.operator == "=" && this.right.is_boolean(); |
+ }); |
+ def(AST_Seq, function(){ |
+ return this.cdr.is_boolean(); |
+ }); |
+ def(AST_True, function(){ return true }); |
+ def(AST_False, function(){ return true }); |
+ })(function(node, func){ |
+ node.DEFMETHOD("is_boolean", func); |
+ }); |
+ |
+ // methods to determine if an expression has a string result type |
+ (function (def){ |
+ def(AST_Node, function(){ return false }); |
+ def(AST_String, function(){ return true }); |
+ def(AST_UnaryPrefix, function(){ |
+ return this.operator == "typeof"; |
+ }); |
+ def(AST_Binary, function(compressor){ |
+ return this.operator == "+" && |
+ (this.left.is_string(compressor) || this.right.is_string(compressor)); |
+ }); |
+ 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_Conditional, function(compressor){ |
+ return this.consequent.is_string(compressor) && this.alternative.is_string(compressor); |
+ }); |
+ def(AST_Call, function(compressor){ |
+ return compressor.option("unsafe") |
+ && this.expression instanceof AST_SymbolRef |
+ && this.expression.name == "String" |
+ && this.expression.undeclared(); |
+ }); |
+ })(function(node, func){ |
+ node.DEFMETHOD("is_string", func); |
+ }); |
+ |
+ function best_of(ast1, ast2) { |
+ return ast1.print_to_string().length > |
+ ast2.print_to_string().length |
+ ? ast2 : ast1; |
+ }; |
+ |
+ // methods to evaluate a constant expression |
+ (function (def){ |
+ // The evaluate method returns an array with one or two |
+ // elements. If the node has been successfully reduced to a |
+ // constant, then the second element tells us the value; |
+ // otherwise the second element is missing. The first element |
+ // of the array is always an AST_Node descendant; if |
+ // evaluation was successful it's a node that represents the |
+ // constant; otherwise it's the original or a replacement node. |
+ AST_Node.DEFMETHOD("evaluate", function(compressor){ |
+ if (!compressor.option("evaluate")) return [ this ]; |
+ try { |
+ var val = this._eval(compressor); |
+ return [ best_of(make_node_from_constant(compressor, val, this), this), val ]; |
+ } catch(ex) { |
+ if (ex !== def) throw ex; |
+ return [ this ]; |
+ } |
+ }); |
+ def(AST_Statement, function(){ |
+ throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start)); |
+ }); |
+ def(AST_Function, function(){ |
+ // XXX: AST_Function inherits from AST_Scope, which itself |
+ // inherits from AST_Statement; however, an AST_Function |
+ // isn't really a statement. This could byte in other |
+ // places too. :-( Wish JS had multiple inheritance. |
+ throw def; |
+ }); |
+ 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_Constant, function(){ |
+ return this.getValue(); |
+ }); |
+ def(AST_UnaryPrefix, function(compressor){ |
+ var e = this.expression; |
+ switch (this.operator) { |
+ case "!": return !ev(e, compressor); |
+ 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; |
+ |
+ return typeof e; |
+ case "void": return void ev(e, compressor); |
+ case "~": return ~ev(e, compressor); |
+ case "-": |
+ e = ev(e, compressor); |
+ if (e === 0) throw def; |
+ return -e; |
+ case "+": return +ev(e, compressor); |
+ } |
+ throw def; |
+ }); |
+ def(AST_Binary, function(c){ |
+ var left = this.left, right = this.right; |
+ switch (this.operator) { |
+ case "&&" : return ev(left, c) && ev(right, c); |
+ case "||" : return ev(left, c) || ev(right, c); |
+ case "|" : return ev(left, c) | ev(right, c); |
+ case "&" : return ev(left, c) & ev(right, c); |
+ case "^" : return ev(left, c) ^ ev(right, c); |
+ case "+" : return ev(left, c) + ev(right, c); |
+ case "*" : return ev(left, c) * ev(right, c); |
+ case "/" : return ev(left, c) / ev(right, c); |
+ case "%" : return ev(left, c) % ev(right, c); |
+ case "-" : return ev(left, c) - ev(right, c); |
+ case "<<" : return ev(left, c) << ev(right, c); |
+ case ">>" : return ev(left, c) >> ev(right, c); |
+ case ">>>" : return ev(left, c) >>> ev(right, c); |
+ case "==" : return ev(left, c) == ev(right, c); |
+ case "===" : return ev(left, c) === ev(right, c); |
+ case "!=" : return ev(left, c) != ev(right, c); |
+ case "!==" : return ev(left, c) !== ev(right, c); |
+ case "<" : return ev(left, c) < ev(right, c); |
+ case "<=" : return ev(left, c) <= ev(right, c); |
+ case ">" : return ev(left, c) > ev(right, c); |
+ case ">=" : return ev(left, c) >= ev(right, c); |
+ case "in" : return ev(left, c) in ev(right, c); |
+ case "instanceof" : return ev(left, c) instanceof ev(right, c); |
+ } |
+ throw def; |
+ }); |
+ def(AST_Conditional, function(compressor){ |
+ return ev(this.condition, compressor) |
+ ? ev(this.consequent, compressor) |
+ : ev(this.alternative, compressor); |
+ }); |
+ def(AST_SymbolRef, function(compressor){ |
+ var d = this.definition(); |
+ if (d && d.constant && d.init) return ev(d.init, compressor); |
+ throw def; |
+ }); |
+ def(AST_Dot, function(compressor){ |
+ if (compressor.option("unsafe") && this.property == "length") { |
+ var str = ev(this.expression, compressor); |
+ if (typeof str == "string") |
+ return str.length; |
+ } |
+ throw def; |
+ }); |
+ })(function(node, func){ |
+ node.DEFMETHOD("_eval", func); |
+ }); |
+ |
+ // method to negate an expression |
+ (function(def){ |
+ function basic_negation(exp) { |
+ return make_node(AST_UnaryPrefix, exp, { |
+ operator: "!", |
+ expression: exp |
+ }); |
+ }; |
+ def(AST_Node, function(){ |
+ return basic_negation(this); |
+ }); |
+ def(AST_Statement, function(){ |
+ throw new Error("Cannot negate a statement"); |
+ }); |
+ def(AST_Function, function(){ |
+ return basic_negation(this); |
+ }); |
+ def(AST_UnaryPrefix, function(){ |
+ if (this.operator == "!") |
+ 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_Conditional, function(compressor){ |
+ var self = this.clone(); |
+ self.consequent = self.consequent.negate(compressor); |
+ self.alternative = self.alternative.negate(compressor); |
+ return best_of(basic_negation(this), self); |
+ }); |
+ def(AST_Binary, function(compressor){ |
+ var self = this.clone(), op = this.operator; |
+ if (compressor.option("unsafe_comps")) { |
+ switch (op) { |
+ case "<=" : self.operator = ">" ; return self; |
+ case "<" : self.operator = ">=" ; return self; |
+ case ">=" : self.operator = "<" ; return self; |
+ case ">" : self.operator = "<=" ; return self; |
+ } |
+ } |
+ switch (op) { |
+ case "==" : self.operator = "!="; return self; |
+ case "!=" : self.operator = "=="; return self; |
+ case "===": self.operator = "!=="; return self; |
+ case "!==": self.operator = "==="; return self; |
+ case "&&": |
+ self.operator = "||"; |
+ self.left = self.left.negate(compressor); |
+ self.right = self.right.negate(compressor); |
+ return best_of(basic_negation(this), self); |
+ case "||": |
+ self.operator = "&&"; |
+ self.left = self.left.negate(compressor); |
+ self.right = self.right.negate(compressor); |
+ return best_of(basic_negation(this), self); |
+ } |
+ return basic_negation(this); |
+ }); |
+ })(function(node, func){ |
+ node.DEFMETHOD("negate", function(compressor){ |
+ return func.call(this, compressor); |
+ }); |
+ }); |
+ |
+ // determine if expression has side effects |
+ (function(def){ |
+ def(AST_Node, function(compressor){ return true }); |
+ |
+ def(AST_EmptyStatement, function(compressor){ return false }); |
+ def(AST_Constant, function(compressor){ return false }); |
+ def(AST_This, function(compressor){ return false }); |
+ |
+ def(AST_Call, function(compressor){ |
+ var pure = compressor.option("pure_funcs"); |
+ if (!pure) return true; |
+ return pure.indexOf(this.expression.print_to_string()) < 0; |
+ }); |
+ |
+ def(AST_Block, function(compressor){ |
+ for (var i = this.body.length; --i >= 0;) { |
+ if (this.body[i].has_side_effects(compressor)) |
+ return true; |
+ } |
+ return false; |
+ }); |
+ |
+ def(AST_SimpleStatement, function(compressor){ |
+ return this.body.has_side_effects(compressor); |
+ }); |
+ def(AST_Defun, function(compressor){ return true }); |
+ def(AST_Function, function(compressor){ return false }); |
+ def(AST_Binary, function(compressor){ |
+ return this.left.has_side_effects(compressor) |
+ || this.right.has_side_effects(compressor); |
+ }); |
+ def(AST_Assign, function(compressor){ return true }); |
+ def(AST_Conditional, function(compressor){ |
+ return this.condition.has_side_effects(compressor) |
+ || this.consequent.has_side_effects(compressor) |
+ || this.alternative.has_side_effects(compressor); |
+ }); |
+ def(AST_Unary, function(compressor){ |
+ return this.operator == "delete" |
+ || this.operator == "++" |
+ || this.operator == "--" |
+ || this.expression.has_side_effects(compressor); |
+ }); |
+ def(AST_SymbolRef, function(compressor){ |
+ return this.global() && this.undeclared(); |
+ }); |
+ def(AST_Object, function(compressor){ |
+ for (var i = this.properties.length; --i >= 0;) |
+ if (this.properties[i].has_side_effects(compressor)) |
+ return true; |
+ return false; |
+ }); |
+ def(AST_ObjectProperty, function(compressor){ |
+ return this.value.has_side_effects(compressor); |
+ }); |
+ def(AST_Array, function(compressor){ |
+ for (var i = this.elements.length; --i >= 0;) |
+ if (this.elements[i].has_side_effects(compressor)) |
+ return true; |
+ return false; |
+ }); |
+ def(AST_Dot, function(compressor){ |
+ if (!compressor.option("pure_getters")) return true; |
+ return this.expression.has_side_effects(compressor); |
+ }); |
+ def(AST_Sub, function(compressor){ |
+ if (!compressor.option("pure_getters")) return true; |
+ return this.expression.has_side_effects(compressor) |
+ || this.property.has_side_effects(compressor); |
+ }); |
+ def(AST_PropAccess, function(compressor){ |
+ return !compressor.option("pure_getters"); |
+ }); |
+ def(AST_Seq, function(compressor){ |
+ return this.car.has_side_effects(compressor) |
+ || this.cdr.has_side_effects(compressor); |
+ }); |
+ })(function(node, func){ |
+ node.DEFMETHOD("has_side_effects", func); |
+ }); |
+ |
+ // tell me if a statement aborts |
+ function aborts(thing) { |
+ return thing && thing.aborts(); |
+ }; |
+ (function(def){ |
+ def(AST_Statement, function(){ return null }); |
+ def(AST_Jump, function(){ return this }); |
+ function block_aborts(){ |
+ var n = this.body.length; |
+ return n > 0 && aborts(this.body[n - 1]); |
+ }; |
+ def(AST_BlockStatement, block_aborts); |
+ def(AST_SwitchBranch, block_aborts); |
+ def(AST_If, function(){ |
+ return this.alternative && aborts(this.body) && aborts(this.alternative); |
+ }); |
+ })(function(node, func){ |
+ node.DEFMETHOD("aborts", func); |
+ }); |
+ |
+ /* -----[ optimizers ]----- */ |
+ |
+ OPT(AST_Directive, function(self, compressor){ |
+ if (self.scope.has_directive(self.value) !== self.scope) { |
+ return make_node(AST_EmptyStatement, self); |
+ } |
+ return self; |
+ }); |
+ |
+ OPT(AST_Debugger, function(self, compressor){ |
+ if (compressor.option("drop_debugger")) |
+ return make_node(AST_EmptyStatement, self); |
+ return self; |
+ }); |
+ |
+ OPT(AST_LabeledStatement, function(self, compressor){ |
+ if (self.body instanceof AST_Break |
+ && compressor.loopcontrol_target(self.body.label) === self.body) { |
+ return make_node(AST_EmptyStatement, self); |
+ } |
+ return self.label.references.length == 0 ? self.body : self; |
+ }); |
+ |
+ OPT(AST_Block, function(self, compressor){ |
+ self.body = tighten_body(self.body, compressor); |
+ return self; |
+ }); |
+ |
+ OPT(AST_BlockStatement, function(self, compressor){ |
+ self.body = tighten_body(self.body, compressor); |
+ switch (self.body.length) { |
+ case 1: return self.body[0]; |
+ case 0: return make_node(AST_EmptyStatement, self); |
+ } |
+ return self; |
+ }); |
+ |
+ AST_Scope.DEFMETHOD("drop_unused", function(compressor){ |
+ var self = this; |
+ if (compressor.option("unused") |
+ && !(self instanceof AST_Toplevel) |
+ && !self.uses_eval |
+ ) { |
+ var in_use = []; |
+ var initializations = new Dictionary(); |
+ // pass 1: find out which symbols are directly used in |
+ // this scope (not in nested scopes). |
+ var scope = this; |
+ var tw = new TreeWalker(function(node, descend){ |
+ if (node !== self) { |
+ if (node instanceof AST_Defun) { |
+ 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 (def.value) { |
+ initializations.add(def.name.name, def.value); |
+ if (def.value.has_side_effects(compressor)) { |
+ def.value.walk(tw); |
+ } |
+ } |
+ }); |
+ return true; |
+ } |
+ if (node instanceof AST_SymbolRef) { |
+ push_uniq(in_use, node.definition()); |
+ return true; |
+ } |
+ 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) { |
+ push_uniq(in_use, node.definition()); |
+ } |
+ }); |
+ 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_Lambda && !(node instanceof AST_Accessor)) { |
+ if (!compressor.option("keep_fargs")) { |
+ for (var a = node.argnames, i = a.length; --i >= 0;) { |
+ var sym = a[i]; |
+ if (sym.unreferenced()) { |
+ a.pop(); |
+ compressor.warn("Dropping unused function argument {name} [{file}:{line},{col}]", { |
+ name : sym.name, |
+ file : sym.start.file, |
+ line : sym.start.line, |
+ col : sym.start.col |
+ }); |
+ } |
+ else break; |
+ } |
+ } |
+ } |
+ if (node instanceof AST_Defun && node !== self) { |
+ if (!member(node.name.definition(), in_use)) { |
+ compressor.warn("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); |
+ } |
+ return node; |
+ } |
+ if (node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn)) { |
+ var def = node.definitions.filter(function(def){ |
+ if (member(def.name.definition(), in_use)) 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.value.has_side_effects(compressor)) { |
+ def._unused_side_effects = true; |
+ compressor.warn("Side effects in initialization of unused variable {name} [{file}:{line},{col}]", w); |
+ return true; |
+ } |
+ compressor.warn("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.value); |
+ def.splice(i, 1); |
+ } else { |
+ if (side_effects.length > 0) { |
+ side_effects.push(x.value); |
+ x.value = AST_Seq.from_array(side_effects); |
+ side_effects = []; |
+ } |
+ ++i; |
+ } |
+ } |
+ 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 { |
+ side_effects = null; |
+ } |
+ if (def.length == 0 && !side_effects) { |
+ return make_node(AST_EmptyStatement, node); |
+ } |
+ if (def.length == 0) { |
+ return side_effects; |
+ } |
+ node.definitions = def; |
+ if (side_effects) { |
+ side_effects.body.unshift(node); |
+ node = side_effects; |
+ } |
+ return node; |
+ } |
+ if (node instanceof AST_For) { |
+ descend(node, this); |
+ |
+ if (node.init instanceof AST_BlockStatement) { |
+ // certain combination of unused name + side effect leads to: |
+ // https://github.com/mishoo/UglifyJS2/issues/44 |
+ // that's an invalid AST. |
+ // We fix it at this stage by moving the `var` outside the `for`. |
+ |
+ var body = node.init.body.slice(0, -1); |
+ node.init = node.init.body.slice(-1)[0].body; |
+ body.push(node); |
+ |
+ return in_list ? MAP.splice(body) : make_node(AST_BlockStatement, node, { |
+ body: body |
+ }); |
+ } |
+ } |
+ if (node instanceof AST_Scope && node !== self) |
+ return node; |
+ } |
+ ); |
+ self.transform(tt); |
+ } |
+ }); |
+ |
+ AST_Scope.DEFMETHOD("hoist_declarations", function(compressor){ |
+ var hoist_funs = compressor.option("hoist_funs"); |
+ var hoist_vars = compressor.option("hoist_vars"); |
+ var self = this; |
+ if (hoist_funs || hoist_vars) { |
+ var dirs = []; |
+ var hoisted = []; |
+ var vars = new Dictionary(), vars_found = 0, var_decl = 0; |
+ // let's count var_decl first, we seem to waste a lot of |
+ // space if we hoist `var` when there's only one. |
+ self.walk(new TreeWalker(function(node){ |
+ if (node instanceof AST_Scope && node !== self) |
+ return true; |
+ if (node instanceof AST_Var) { |
+ ++var_decl; |
+ return true; |
+ } |
+ })); |
+ hoist_vars = hoist_vars && var_decl > 1; |
+ var tt = new TreeTransformer( |
+ function before(node) { |
+ if (node !== self) { |
+ if (node instanceof AST_Directive) { |
+ dirs.push(node); |
+ return make_node(AST_EmptyStatement, node); |
+ } |
+ if (node instanceof AST_Defun && hoist_funs) { |
+ hoisted.push(node); |
+ return make_node(AST_EmptyStatement, node); |
+ } |
+ if (node instanceof AST_Var && hoist_vars) { |
+ node.definitions.forEach(function(def){ |
+ vars.set(def.name.name, def); |
+ ++vars_found; |
+ }); |
+ var seq = node.to_assignments(); |
+ var p = tt.parent(); |
+ if (p instanceof AST_ForIn && p.init === node) { |
+ if (seq == null) return node.definitions[0].name; |
+ return seq; |
+ } |
+ if (p instanceof AST_For && p.init === node) { |
+ return seq; |
+ } |
+ if (!seq) return make_node(AST_EmptyStatement, node); |
+ return make_node(AST_SimpleStatement, node, { |
+ body: seq |
+ }); |
+ } |
+ if (node instanceof AST_Scope) |
+ return node; // to avoid descending in nested scopes |
+ } |
+ } |
+ ); |
+ self = self.transform(tt); |
+ if (vars_found > 0) { |
+ // collect only vars which don't show up in self's arguments list |
+ var defs = []; |
+ vars.each(function(def, name){ |
+ if (self instanceof AST_Lambda |
+ && find_if(function(x){ return x.name == def.name.name }, |
+ self.argnames)) { |
+ vars.del(name); |
+ } else { |
+ def = def.clone(); |
+ def.value = null; |
+ defs.push(def); |
+ vars.set(name, def); |
+ } |
+ }); |
+ if (defs.length > 0) { |
+ // try to merge in assignments |
+ for (var i = 0; i < self.body.length;) { |
+ if (self.body[i] instanceof AST_SimpleStatement) { |
+ var expr = self.body[i].body, sym, assign; |
+ if (expr instanceof AST_Assign |
+ && expr.operator == "=" |
+ && (sym = expr.left) instanceof AST_Symbol |
+ && vars.has(sym.name)) |
+ { |
+ var def = vars.get(sym.name); |
+ if (def.value) break; |
+ def.value = expr.right; |
+ remove(defs, def); |
+ defs.push(def); |
+ self.body.splice(i, 1); |
+ continue; |
+ } |
+ if (expr instanceof AST_Seq |
+ && (assign = expr.car) instanceof AST_Assign |
+ && assign.operator == "=" |
+ && (sym = assign.left) instanceof AST_Symbol |
+ && vars.has(sym.name)) |
+ { |
+ var def = vars.get(sym.name); |
+ if (def.value) break; |
+ def.value = assign.right; |
+ remove(defs, def); |
+ defs.push(def); |
+ self.body[i].body = expr.cdr; |
+ continue; |
+ } |
+ } |
+ if (self.body[i] instanceof AST_EmptyStatement) { |
+ self.body.splice(i, 1); |
+ continue; |
+ } |
+ if (self.body[i] instanceof AST_BlockStatement) { |
+ var tmp = [ i, 1 ].concat(self.body[i].body); |
+ self.body.splice.apply(self.body, tmp); |
+ continue; |
+ } |
+ break; |
+ } |
+ defs = make_node(AST_Var, self, { |
+ definitions: defs |
+ }); |
+ hoisted.push(defs); |
+ }; |
+ } |
+ self.body = dirs.concat(hoisted, self.body); |
+ } |
+ return self; |
+ }); |
+ |
+ OPT(AST_SimpleStatement, function(self, compressor){ |
+ if (compressor.option("side_effects")) { |
+ if (!self.body.has_side_effects(compressor)) { |
+ compressor.warn("Dropping side-effect-free statement [{file}:{line},{col}]", self.start); |
+ return make_node(AST_EmptyStatement, self); |
+ } |
+ } |
+ return self; |
+ }); |
+ |
+ OPT(AST_DWLoop, function(self, compressor){ |
+ var cond = self.condition.evaluate(compressor); |
+ self.condition = cond[0]; |
+ if (!compressor.option("loops")) return self; |
+ if (cond.length > 1) { |
+ if (cond[1]) { |
+ return make_node(AST_For, self, { |
+ body: self.body |
+ }); |
+ } else if (self instanceof AST_While) { |
+ if (compressor.option("dead_code")) { |
+ var a = []; |
+ extract_declarations_from_unreachable_code(compressor, self.body, a); |
+ return make_node(AST_BlockStatement, self, { body: a }); |
+ } |
+ } |
+ } |
+ return self; |
+ }); |
+ |
+ function if_break_in_loop(self, compressor) { |
+ function drop_it(rest) { |
+ rest = as_statement_array(rest); |
+ if (self.body instanceof AST_BlockStatement) { |
+ self.body = self.body.clone(); |
+ self.body.body = rest.concat(self.body.body.slice(1)); |
+ self.body = self.body.transform(compressor); |
+ } else { |
+ self.body = make_node(AST_BlockStatement, self.body, { |
+ body: rest |
+ }).transform(compressor); |
+ } |
+ if_break_in_loop(self, compressor); |
+ } |
+ var first = self.body instanceof AST_BlockStatement ? self.body.body[0] : self.body; |
+ if (first instanceof AST_If) { |
+ if (first.body instanceof AST_Break |
+ && compressor.loopcontrol_target(first.body.label) === self) { |
+ if (self.condition) { |
+ self.condition = make_node(AST_Binary, self.condition, { |
+ left: self.condition, |
+ operator: "&&", |
+ right: first.condition.negate(compressor), |
+ }); |
+ } else { |
+ self.condition = first.condition.negate(compressor); |
+ } |
+ drop_it(first.alternative); |
+ } |
+ else if (first.alternative instanceof AST_Break |
+ && compressor.loopcontrol_target(first.alternative.label) === self) { |
+ if (self.condition) { |
+ self.condition = make_node(AST_Binary, self.condition, { |
+ left: self.condition, |
+ operator: "&&", |
+ right: first.condition, |
+ }); |
+ } else { |
+ self.condition = first.condition; |
+ } |
+ drop_it(first.body); |
+ } |
+ } |
+ }; |
+ |
+ OPT(AST_While, function(self, compressor) { |
+ if (!compressor.option("loops")) return self; |
+ self = AST_DWLoop.prototype.optimize.call(self, compressor); |
+ if (self instanceof AST_While) { |
+ if_break_in_loop(self, compressor); |
+ self = make_node(AST_For, self, self).transform(compressor); |
+ } |
+ return self; |
+ }); |
+ |
+ OPT(AST_For, function(self, compressor){ |
+ var cond = self.condition; |
+ if (cond) { |
+ cond = cond.evaluate(compressor); |
+ self.condition = cond[0]; |
+ } |
+ if (!compressor.option("loops")) return self; |
+ if (cond) { |
+ if (cond.length > 1 && !cond[1]) { |
+ if (compressor.option("dead_code")) { |
+ var a = []; |
+ if (self.init instanceof AST_Statement) { |
+ a.push(self.init); |
+ } |
+ else if (self.init) { |
+ a.push(make_node(AST_SimpleStatement, self.init, { |
+ body: self.init |
+ })); |
+ } |
+ extract_declarations_from_unreachable_code(compressor, self.body, a); |
+ return make_node(AST_BlockStatement, self, { body: a }); |
+ } |
+ } |
+ } |
+ if_break_in_loop(self, compressor); |
+ return self; |
+ }); |
+ |
+ OPT(AST_If, function(self, compressor){ |
+ if (!compressor.option("conditionals")) return self; |
+ // if condition can be statically determined, warn and drop |
+ // one of the blocks. note, statically determined implies |
+ // “has no side effects”; also it doesn't work for cases like |
+ // `x && true`, though it probably should. |
+ var cond = self.condition.evaluate(compressor); |
+ self.condition = cond[0]; |
+ if (cond.length > 1) { |
+ if (cond[1]) { |
+ compressor.warn("Condition always true [{file}:{line},{col}]", self.condition.start); |
+ if (compressor.option("dead_code")) { |
+ var a = []; |
+ if (self.alternative) { |
+ extract_declarations_from_unreachable_code(compressor, self.alternative, a); |
+ } |
+ a.push(self.body); |
+ return make_node(AST_BlockStatement, self, { body: a }).transform(compressor); |
+ } |
+ } else { |
+ compressor.warn("Condition always false [{file}:{line},{col}]", self.condition.start); |
+ if (compressor.option("dead_code")) { |
+ var a = []; |
+ extract_declarations_from_unreachable_code(compressor, self.body, a); |
+ if (self.alternative) a.push(self.alternative); |
+ return make_node(AST_BlockStatement, self, { body: a }).transform(compressor); |
+ } |
+ } |
+ } |
+ if (is_empty(self.alternative)) self.alternative = null; |
+ var negated = self.condition.negate(compressor); |
+ var negated_is_best = best_of(self.condition, negated) === negated; |
+ if (self.alternative && negated_is_best) { |
+ negated_is_best = false; // because we already do the switch here. |
+ self.condition = negated; |
+ var tmp = self.body; |
+ self.body = self.alternative || make_node(AST_EmptyStatement); |
+ self.alternative = tmp; |
+ } |
+ if (is_empty(self.body) && is_empty(self.alternative)) { |
+ return make_node(AST_SimpleStatement, self.condition, { |
+ body: self.condition |
+ }).transform(compressor); |
+ } |
+ if (self.body instanceof AST_SimpleStatement |
+ && self.alternative instanceof AST_SimpleStatement) { |
+ return make_node(AST_SimpleStatement, self, { |
+ body: make_node(AST_Conditional, self, { |
+ condition : self.condition, |
+ consequent : self.body.body, |
+ alternative : self.alternative.body |
+ }) |
+ }).transform(compressor); |
+ } |
+ if (is_empty(self.alternative) && self.body instanceof AST_SimpleStatement) { |
+ if (negated_is_best) return make_node(AST_SimpleStatement, self, { |
+ body: make_node(AST_Binary, self, { |
+ operator : "||", |
+ left : negated, |
+ right : self.body.body |
+ }) |
+ }).transform(compressor); |
+ return make_node(AST_SimpleStatement, self, { |
+ body: make_node(AST_Binary, self, { |
+ operator : "&&", |
+ left : self.condition, |
+ right : self.body.body |
+ }) |
+ }).transform(compressor); |
+ } |
+ if (self.body instanceof AST_EmptyStatement |
+ && self.alternative |
+ && self.alternative instanceof AST_SimpleStatement) { |
+ return make_node(AST_SimpleStatement, self, { |
+ body: make_node(AST_Binary, self, { |
+ operator : "||", |
+ left : self.condition, |
+ right : self.alternative.body |
+ }) |
+ }).transform(compressor); |
+ } |
+ if (self.body instanceof AST_Exit |
+ && self.alternative instanceof AST_Exit |
+ && self.body.TYPE == self.alternative.TYPE) { |
+ return make_node(self.body.CTOR, self, { |
+ value: make_node(AST_Conditional, self, { |
+ condition : self.condition, |
+ consequent : self.body.value || make_node(AST_Undefined, self.body).optimize(compressor), |
+ alternative : self.alternative.value || make_node(AST_Undefined, self.alternative).optimize(compressor) |
+ }) |
+ }).transform(compressor); |
+ } |
+ if (self.body instanceof AST_If |
+ && !self.body.alternative |
+ && !self.alternative) { |
+ self.condition = make_node(AST_Binary, self.condition, { |
+ operator: "&&", |
+ left: self.condition, |
+ right: self.body.condition |
+ }).transform(compressor); |
+ self.body = self.body.body; |
+ } |
+ if (aborts(self.body)) { |
+ if (self.alternative) { |
+ var alt = self.alternative; |
+ self.alternative = null; |
+ return make_node(AST_BlockStatement, self, { |
+ body: [ self, alt ] |
+ }).transform(compressor); |
+ } |
+ } |
+ if (aborts(self.alternative)) { |
+ var body = self.body; |
+ self.body = self.alternative; |
+ self.condition = negated_is_best ? negated : self.condition.negate(compressor); |
+ self.alternative = null; |
+ return make_node(AST_BlockStatement, self, { |
+ body: [ self, body ] |
+ }).transform(compressor); |
+ } |
+ return self; |
+ }); |
+ |
+ OPT(AST_Switch, function(self, compressor){ |
+ if (self.body.length == 0 && compressor.option("conditionals")) { |
+ return make_node(AST_SimpleStatement, self, { |
+ body: self.expression |
+ }).transform(compressor); |
+ } |
+ for(;;) { |
+ var last_branch = self.body[self.body.length - 1]; |
+ if (last_branch) { |
+ var stat = last_branch.body[last_branch.body.length - 1]; // last statement |
+ if (stat instanceof AST_Break && loop_body(compressor.loopcontrol_target(stat.label)) === self) |
+ last_branch.body.pop(); |
+ if (last_branch instanceof AST_Default && last_branch.body.length == 0) { |
+ self.body.pop(); |
+ continue; |
+ } |
+ } |
+ break; |
+ } |
+ var exp = self.expression.evaluate(compressor); |
+ out: if (exp.length == 2) try { |
+ // constant expression |
+ self.expression = exp[0]; |
+ if (!compressor.option("dead_code")) break out; |
+ var value = exp[1]; |
+ var in_if = false; |
+ var in_block = false; |
+ var started = false; |
+ var stopped = false; |
+ var ruined = false; |
+ var tt = new TreeTransformer(function(node, descend, in_list){ |
+ if (node instanceof AST_Lambda || node instanceof AST_SimpleStatement) { |
+ // no need to descend these node types |
+ return node; |
+ } |
+ else if (node instanceof AST_Switch && node === self) { |
+ node = node.clone(); |
+ descend(node, this); |
+ return ruined ? node : make_node(AST_BlockStatement, node, { |
+ body: node.body.reduce(function(a, branch){ |
+ return a.concat(branch.body); |
+ }, []) |
+ }).transform(compressor); |
+ } |
+ else if (node instanceof AST_If || node instanceof AST_Try) { |
+ var save = in_if; |
+ in_if = !in_block; |
+ descend(node, this); |
+ in_if = save; |
+ return node; |
+ } |
+ else if (node instanceof AST_StatementWithBody || node instanceof AST_Switch) { |
+ var save = in_block; |
+ in_block = true; |
+ descend(node, this); |
+ in_block = save; |
+ return node; |
+ } |
+ else if (node instanceof AST_Break && this.loopcontrol_target(node.label) === self) { |
+ if (in_if) { |
+ ruined = true; |
+ return node; |
+ } |
+ if (in_block) return node; |
+ stopped = true; |
+ return in_list ? MAP.skip : make_node(AST_EmptyStatement, node); |
+ } |
+ else if (node instanceof AST_SwitchBranch && this.parent() === self) { |
+ if (stopped) return MAP.skip; |
+ if (node instanceof AST_Case) { |
+ var exp = node.expression.evaluate(compressor); |
+ if (exp.length < 2) { |
+ // got a case with non-constant expression, baling out |
+ throw self; |
+ } |
+ if (exp[1] === value || started) { |
+ started = true; |
+ if (aborts(node)) stopped = true; |
+ descend(node, this); |
+ return node; |
+ } |
+ return MAP.skip; |
+ } |
+ descend(node, this); |
+ return node; |
+ } |
+ }); |
+ tt.stack = compressor.stack.slice(); // so that's able to see parent nodes |
+ self = self.transform(tt); |
+ } catch(ex) { |
+ if (ex !== self) throw ex; |
+ } |
+ return self; |
+ }); |
+ |
+ OPT(AST_Case, function(self, compressor){ |
+ self.body = tighten_body(self.body, compressor); |
+ return self; |
+ }); |
+ |
+ OPT(AST_Try, function(self, compressor){ |
+ self.body = tighten_body(self.body, compressor); |
+ return self; |
+ }); |
+ |
+ AST_Definitions.DEFMETHOD("remove_initializers", function(){ |
+ this.definitions.forEach(function(def){ def.value = null }); |
+ }); |
+ |
+ AST_Definitions.DEFMETHOD("to_assignments", function(){ |
+ var assignments = this.definitions.reduce(function(a, def){ |
+ if (def.value) { |
+ var name = make_node(AST_SymbolRef, def.name, def.name); |
+ a.push(make_node(AST_Assign, def, { |
+ operator : "=", |
+ left : name, |
+ right : def.value |
+ })); |
+ } |
+ return a; |
+ }, []); |
+ if (assignments.length == 0) return null; |
+ return AST_Seq.from_array(assignments); |
+ }); |
+ |
+ OPT(AST_Definitions, function(self, compressor){ |
+ if (self.definitions.length == 0) |
+ return make_node(AST_EmptyStatement, self); |
+ return self; |
+ }); |
+ |
+ OPT(AST_Function, function(self, compressor){ |
+ self = AST_Lambda.prototype.optimize.call(self, compressor); |
+ if (compressor.option("unused")) { |
+ if (self.name && self.name.unreferenced()) { |
+ self.name = null; |
+ } |
+ } |
+ return self; |
+ }); |
+ |
+ OPT(AST_Call, function(self, compressor){ |
+ if (compressor.option("unsafe")) { |
+ var exp = self.expression; |
+ if (exp instanceof AST_SymbolRef && exp.undeclared()) { |
+ switch (exp.name) { |
+ case "Array": |
+ if (self.args.length != 1) { |
+ return make_node(AST_Array, self, { |
+ elements: self.args |
+ }).transform(compressor); |
+ } |
+ break; |
+ case "Object": |
+ if (self.args.length == 0) { |
+ return make_node(AST_Object, self, { |
+ properties: [] |
+ }); |
+ } |
+ break; |
+ case "String": |
+ if (self.args.length == 0) return make_node(AST_String, self, { |
+ value: "" |
+ }); |
+ if (self.args.length <= 1) return make_node(AST_Binary, self, { |
+ left: self.args[0], |
+ operator: "+", |
+ right: make_node(AST_String, self, { value: "" }) |
+ }).transform(compressor); |
+ break; |
+ case "Number": |
+ if (self.args.length == 0) return make_node(AST_Number, self, { |
+ value: 0 |
+ }); |
+ if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, { |
+ expression: self.args[0], |
+ operator: "+" |
+ }).transform(compressor); |
+ case "Boolean": |
+ if (self.args.length == 0) return make_node(AST_False, self); |
+ if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, { |
+ expression: make_node(AST_UnaryPrefix, null, { |
+ expression: self.args[0], |
+ operator: "!" |
+ }), |
+ operator: "!" |
+ }).transform(compressor); |
+ break; |
+ case "Function": |
+ 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) { |
+ return make_node(AST_Binary, self, { |
+ left: make_node(AST_String, self, { value: "" }), |
+ operator: "+", |
+ right: exp.expression |
+ }).transform(compressor); |
+ } |
+ else if (exp instanceof AST_Dot && exp.expression instanceof AST_Array && exp.property == "join") EXIT: { |
+ var separator = self.args.length == 0 ? "," : self.args[0].evaluate(compressor)[1]; |
+ if (separator == null) break EXIT; // not a constant |
+ var elements = exp.expression.elements.reduce(function(a, el){ |
+ el = el.evaluate(compressor); |
+ if (a.length == 0 || el.length == 1) { |
+ a.push(el); |
+ } else { |
+ var last = a[a.length - 1]; |
+ if (last.length == 2) { |
+ // it's a constant |
+ var val = "" + last[1] + separator + el[1]; |
+ a[a.length - 1] = [ make_node_from_constant(compressor, val, last[0]), val ]; |
+ } else { |
+ a.push(el); |
+ } |
+ } |
+ return a; |
+ }, []); |
+ if (elements.length == 0) return make_node(AST_String, self, { value: "" }); |
+ if (elements.length == 1) return elements[0][0]; |
+ if (separator == "") { |
+ var first; |
+ if (elements[0][0] instanceof AST_String |
+ || elements[1][0] instanceof AST_String) { |
+ first = elements.shift()[0]; |
+ } else { |
+ first = make_node(AST_String, self, { value: "" }); |
+ } |
+ return elements.reduce(function(prev, el){ |
+ return make_node(AST_Binary, el[0], { |
+ operator : "+", |
+ left : prev, |
+ right : el[0], |
+ }); |
+ }, first).transform(compressor); |
+ } |
+ // need this awkward cloning to not affect original element |
+ // best_of will decide which one to get through. |
+ var node = self.clone(); |
+ node.expression = node.expression.clone(); |
+ node.expression.expression = node.expression.expression.clone(); |
+ node.expression.expression.elements = elements.map(function(el){ |
+ return el[0]; |
+ }); |
+ return best_of(self, node); |
+ } |
+ } |
+ if (compressor.option("side_effects")) { |
+ if (self.expression instanceof AST_Function |
+ && self.args.length == 0 |
+ && !AST_Block.prototype.has_side_effects.call(self.expression, compressor)) { |
+ return make_node(AST_Undefined, self).transform(compressor); |
+ } |
+ } |
+ if (compressor.option("drop_console")) { |
+ if (self.expression instanceof AST_PropAccess && |
+ self.expression.expression instanceof AST_SymbolRef && |
+ self.expression.expression.name == "console" && |
+ self.expression.expression.undeclared()) { |
+ return make_node(AST_Undefined, self).transform(compressor); |
+ } |
+ } |
+ return self.evaluate(compressor)[0]; |
+ }); |
+ |
+ OPT(AST_New, function(self, compressor){ |
+ if (compressor.option("unsafe")) { |
+ var exp = self.expression; |
+ if (exp instanceof AST_SymbolRef && exp.undeclared()) { |
+ switch (exp.name) { |
+ case "Object": |
+ case "RegExp": |
+ case "Function": |
+ case "Error": |
+ case "Array": |
+ return make_node(AST_Call, self, self).transform(compressor); |
+ } |
+ } |
+ } |
+ return self; |
+ }); |
+ |
+ OPT(AST_Seq, function(self, compressor){ |
+ if (!compressor.option("side_effects")) |
+ return self; |
+ if (!self.car.has_side_effects(compressor)) { |
+ // we shouldn't compress (1,eval)(something) to |
+ // eval(something) because that changes the meaning of |
+ // eval (becomes lexical instead of global). |
+ var p; |
+ if (!(self.cdr instanceof AST_SymbolRef |
+ && self.cdr.name == "eval" |
+ && self.cdr.undeclared() |
+ && (p = compressor.parent()) instanceof AST_Call |
+ && p.expression === self)) { |
+ return self.cdr; |
+ } |
+ } |
+ if (compressor.option("cascade")) { |
+ if (self.car instanceof AST_Assign |
+ && !self.car.left.has_side_effects(compressor)) { |
+ if (self.car.left.equivalent_to(self.cdr)) { |
+ return self.car; |
+ } |
+ if (self.cdr instanceof AST_Call |
+ && self.cdr.expression.equivalent_to(self.car.left)) { |
+ self.cdr.expression = self.car; |
+ return self.cdr; |
+ } |
+ } |
+ if (!self.car.has_side_effects(compressor) |
+ && !self.cdr.has_side_effects(compressor) |
+ && self.car.equivalent_to(self.cdr)) { |
+ return self.car; |
+ } |
+ } |
+ if (self.cdr instanceof AST_UnaryPrefix |
+ && self.cdr.operator == "void" |
+ && !self.cdr.expression.has_side_effects(compressor)) { |
+ self.cdr.operator = self.car; |
+ return self.cdr; |
+ } |
+ if (self.cdr instanceof AST_Undefined) { |
+ 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(); |
+ this.expression = x.pop(); |
+ x.push(this); |
+ seq = AST_Seq.from_array(x).transform(compressor); |
+ return seq; |
+ } |
+ } |
+ return this; |
+ }); |
+ |
+ OPT(AST_UnaryPostfix, function(self, compressor){ |
+ return self.lift_sequences(compressor); |
+ }); |
+ |
+ OPT(AST_UnaryPrefix, function(self, compressor){ |
+ self = self.lift_sequences(compressor); |
+ var e = self.expression; |
+ if (compressor.option("booleans") && compressor.in_boolean_context()) { |
+ switch (self.operator) { |
+ case "!": |
+ if (e instanceof AST_UnaryPrefix && e.operator == "!") { |
+ // !!foo ==> foo, if we're in boolean context |
+ return e.expression; |
+ } |
+ break; |
+ case "typeof": |
+ // 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 make_node(AST_True, self); |
+ } |
+ if (e instanceof AST_Binary && self.operator == "!") { |
+ self = best_of(self, e.negate(compressor)); |
+ } |
+ } |
+ return self.evaluate(compressor)[0]; |
+ }); |
+ |
+ function has_side_effects_or_prop_access(node, compressor) { |
+ var save_pure_getters = compressor.option("pure_getters"); |
+ compressor.options.pure_getters = false; |
+ var ret = node.has_side_effects(compressor); |
+ compressor.options.pure_getters = save_pure_getters; |
+ return ret; |
+ } |
+ |
+ 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(); |
+ this.left = x.pop(); |
+ x.push(this); |
+ seq = AST_Seq.from_array(x).transform(compressor); |
+ return seq; |
+ } |
+ if (this.right instanceof AST_Seq |
+ && this instanceof AST_Assign |
+ && !has_side_effects_or_prop_access(this.left, compressor)) { |
+ var seq = this.right; |
+ var x = seq.to_array(); |
+ this.right = x.pop(); |
+ x.push(this); |
+ seq = AST_Seq.from_array(x).transform(compressor); |
+ return seq; |
+ } |
+ } |
+ return this; |
+ }); |
+ |
+ var commutativeOperators = makePredicate("== === != !== * & | ^"); |
+ |
+ OPT(AST_Binary, function(self, compressor){ |
+ var reverse = compressor.has_directive("use asm") ? noop |
+ : function(op, force) { |
+ if (force || !(self.left.has_side_effects(compressor) || self.right.has_side_effects(compressor))) { |
+ if (op) self.operator = op; |
+ var tmp = self.left; |
+ self.left = self.right; |
+ self.right = tmp; |
+ } |
+ }; |
+ if (commutativeOperators(self.operator)) { |
+ if (self.right instanceof AST_Constant |
+ && !(self.left instanceof AST_Constant)) { |
+ // if right is a constant, whatever side effects the |
+ // left side might have could not influence the |
+ // result. hence, force switch. |
+ |
+ if (!(self.left instanceof AST_Binary |
+ && PRECEDENCE[self.left.operator] >= PRECEDENCE[self.operator])) { |
+ reverse(null, true); |
+ } |
+ } |
+ if (/^[!=]==?$/.test(self.operator)) { |
+ if (self.left instanceof AST_SymbolRef && self.right instanceof AST_Conditional) { |
+ if (self.right.consequent instanceof AST_SymbolRef |
+ && self.right.consequent.definition() === self.left.definition()) { |
+ if (/^==/.test(self.operator)) return self.right.condition; |
+ if (/^!=/.test(self.operator)) return self.right.condition.negate(compressor); |
+ } |
+ if (self.right.alternative instanceof AST_SymbolRef |
+ && self.right.alternative.definition() === self.left.definition()) { |
+ if (/^==/.test(self.operator)) return self.right.condition.negate(compressor); |
+ if (/^!=/.test(self.operator)) return self.right.condition; |
+ } |
+ } |
+ if (self.right instanceof AST_SymbolRef && self.left instanceof AST_Conditional) { |
+ if (self.left.consequent instanceof AST_SymbolRef |
+ && self.left.consequent.definition() === self.right.definition()) { |
+ if (/^==/.test(self.operator)) return self.left.condition; |
+ if (/^!=/.test(self.operator)) return self.left.condition.negate(compressor); |
+ } |
+ if (self.left.alternative instanceof AST_SymbolRef |
+ && self.left.alternative.definition() === self.right.definition()) { |
+ if (/^==/.test(self.operator)) return self.left.condition.negate(compressor); |
+ if (/^!=/.test(self.operator)) return self.left.condition; |
+ } |
+ } |
+ } |
+ } |
+ self = self.lift_sequences(compressor); |
+ if (compressor.option("comparisons")) switch (self.operator) { |
+ case "===": |
+ case "!==": |
+ if ((self.left.is_string(compressor) && self.right.is_string(compressor)) || |
+ (self.left.is_boolean() && self.right.is_boolean())) { |
+ self.operator = self.operator.substr(0, 2); |
+ } |
+ // XXX: intentionally falling down to the next case |
+ case "==": |
+ case "!=": |
+ if (self.left instanceof AST_String |
+ && self.left.value == "undefined" |
+ && self.right instanceof AST_UnaryPrefix |
+ && self.right.operator == "typeof" |
+ && compressor.option("unsafe")) { |
+ if (!(self.right.expression instanceof AST_SymbolRef) |
+ || !self.right.expression.undeclared()) { |
+ self.right = self.right.expression; |
+ self.left = make_node(AST_Undefined, self.left).optimize(compressor); |
+ if (self.operator.length == 2) self.operator += "="; |
+ } |
+ } |
+ break; |
+ } |
+ if (compressor.option("booleans") && compressor.in_boolean_context()) switch (self.operator) { |
+ case "&&": |
+ var ll = self.left.evaluate(compressor); |
+ var rr = self.right.evaluate(compressor); |
+ if ((ll.length > 1 && !ll[1]) || (rr.length > 1 && !rr[1])) { |
+ compressor.warn("Boolean && always false [{file}:{line},{col}]", self.start); |
+ return make_node(AST_False, self); |
+ } |
+ if (ll.length > 1 && ll[1]) { |
+ return rr[0]; |
+ } |
+ if (rr.length > 1 && rr[1]) { |
+ return ll[0]; |
+ } |
+ break; |
+ case "||": |
+ var ll = self.left.evaluate(compressor); |
+ var rr = self.right.evaluate(compressor); |
+ if ((ll.length > 1 && ll[1]) || (rr.length > 1 && rr[1])) { |
+ compressor.warn("Boolean || always true [{file}:{line},{col}]", self.start); |
+ return make_node(AST_True, self); |
+ } |
+ if (ll.length > 1 && !ll[1]) { |
+ return rr[0]; |
+ } |
+ if (rr.length > 1 && !rr[1]) { |
+ return ll[0]; |
+ } |
+ break; |
+ case "+": |
+ var ll = self.left.evaluate(compressor); |
+ var rr = self.right.evaluate(compressor); |
+ if ((ll.length > 1 && ll[0] instanceof AST_String && ll[1]) || |
+ (rr.length > 1 && rr[0] instanceof AST_String && rr[1])) { |
+ compressor.warn("+ in boolean context always true [{file}:{line},{col}]", self.start); |
+ return make_node(AST_True, self); |
+ } |
+ break; |
+ } |
+ if (compressor.option("comparisons")) { |
+ if (!(compressor.parent() instanceof AST_Binary) |
+ || compressor.parent() instanceof AST_Assign) { |
+ var negated = make_node(AST_UnaryPrefix, self, { |
+ operator: "!", |
+ expression: self.negate(compressor) |
+ }); |
+ self = best_of(self, negated); |
+ } |
+ switch (self.operator) { |
+ case "<": reverse(">"); break; |
+ case "<=": reverse(">="); break; |
+ } |
+ } |
+ if (self.operator == "+" && self.right instanceof AST_String |
+ && self.right.getValue() === "" && self.left instanceof AST_Binary |
+ && self.left.operator == "+" && self.left.is_string(compressor)) { |
+ return self.left; |
+ } |
+ if (compressor.option("evaluate")) { |
+ if (self.operator == "+") { |
+ if (self.left instanceof AST_Constant |
+ && self.right instanceof AST_Binary |
+ && self.right.operator == "+" |
+ && self.right.left instanceof AST_Constant |
+ && self.right.is_string(compressor)) { |
+ self = make_node(AST_Binary, self, { |
+ operator: "+", |
+ left: make_node(AST_String, null, { |
+ value: "" + self.left.getValue() + self.right.left.getValue(), |
+ start: self.left.start, |
+ end: self.right.left.end |
+ }), |
+ right: self.right.right |
+ }); |
+ } |
+ if (self.right instanceof AST_Constant |
+ && self.left instanceof AST_Binary |
+ && self.left.operator == "+" |
+ && self.left.right instanceof AST_Constant |
+ && self.left.is_string(compressor)) { |
+ self = make_node(AST_Binary, self, { |
+ operator: "+", |
+ left: self.left.left, |
+ right: make_node(AST_String, null, { |
+ value: "" + self.left.right.getValue() + self.right.getValue(), |
+ start: self.left.right.start, |
+ end: self.right.end |
+ }) |
+ }); |
+ } |
+ if (self.left instanceof AST_Binary |
+ && self.left.operator == "+" |
+ && self.left.is_string(compressor) |
+ && self.left.right instanceof AST_Constant |
+ && self.right instanceof AST_Binary |
+ && self.right.operator == "+" |
+ && self.right.left instanceof AST_Constant |
+ && self.right.is_string(compressor)) { |
+ self = make_node(AST_Binary, self, { |
+ operator: "+", |
+ left: make_node(AST_Binary, self.left, { |
+ operator: "+", |
+ left: self.left.left, |
+ right: make_node(AST_String, null, { |
+ value: "" + self.left.right.getValue() + self.right.left.getValue(), |
+ start: self.left.right.start, |
+ end: self.right.left.end |
+ }) |
+ }), |
+ right: self.right.right |
+ }); |
+ } |
+ } |
+ } |
+ // x * (y * z) ==> x * y * z |
+ if (self.right instanceof AST_Binary |
+ && self.right.operator == self.operator |
+ && (self.operator == "*" || self.operator == "&&" || self.operator == "||")) |
+ { |
+ self.left = make_node(AST_Binary, self.left, { |
+ operator : self.operator, |
+ left : self.left, |
+ right : self.right.left |
+ }); |
+ self.right = self.right.right; |
+ return self.transform(compressor); |
+ } |
+ return self.evaluate(compressor)[0]; |
+ }); |
+ |
+ OPT(AST_SymbolRef, function(self, compressor){ |
+ if (self.undeclared()) { |
+ var defines = compressor.option("global_defs"); |
+ if (defines && defines.hasOwnProperty(self.name)) { |
+ return make_node_from_constant(compressor, defines[self.name], self); |
+ } |
+ switch (self.name) { |
+ case "undefined": |
+ return make_node(AST_Undefined, self); |
+ case "NaN": |
+ return make_node(AST_NaN, self); |
+ case "Infinity": |
+ return make_node(AST_Infinity, self); |
+ } |
+ } |
+ return self; |
+ }); |
+ |
+ OPT(AST_Undefined, function(self, compressor){ |
+ if (compressor.option("unsafe")) { |
+ var scope = compressor.find_parent(AST_Scope); |
+ var undef = scope.find_variable("undefined"); |
+ if (undef) { |
+ var ref = make_node(AST_SymbolRef, self, { |
+ name : "undefined", |
+ scope : scope, |
+ thedef : undef |
+ }); |
+ ref.reference(); |
+ return ref; |
+ } |
+ } |
+ return self; |
+ }); |
+ |
+ var ASSIGN_OPS = [ '+', '-', '/', '*', '%', '>>', '<<', '>>>', '|', '^', '&' ]; |
+ OPT(AST_Assign, function(self, compressor){ |
+ self = self.lift_sequences(compressor); |
+ if (self.operator == "=" |
+ && self.left instanceof AST_SymbolRef |
+ && self.right instanceof AST_Binary |
+ && self.right.left instanceof AST_SymbolRef |
+ && self.right.left.name == self.left.name |
+ && member(self.right.operator, ASSIGN_OPS)) { |
+ self.operator = self.right.operator + "="; |
+ self.right = self.right.right; |
+ } |
+ return self; |
+ }); |
+ |
+ 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); |
+ } |
+ var cond = self.condition.evaluate(compressor); |
+ if (cond.length > 1) { |
+ if (cond[1]) { |
+ compressor.warn("Condition always true [{file}:{line},{col}]", self.start); |
+ return self.consequent; |
+ } else { |
+ compressor.warn("Condition always false [{file}:{line},{col}]", self.start); |
+ return self.alternative; |
+ } |
+ } |
+ var negated = cond[0].negate(compressor); |
+ if (best_of(cond[0], negated) === negated) { |
+ self = make_node(AST_Conditional, self, { |
+ condition: negated, |
+ consequent: self.alternative, |
+ alternative: self.consequent |
+ }); |
+ } |
+ var consequent = self.consequent; |
+ var alternative = self.alternative; |
+ if (consequent instanceof AST_Assign |
+ && alternative instanceof AST_Assign |
+ && consequent.operator == alternative.operator |
+ && consequent.left.equivalent_to(alternative.left) |
+ ) { |
+ /* |
+ * Stuff like this: |
+ * if (foo) exp = something; else exp = something_else; |
+ * ==> |
+ * exp = foo ? something : something_else; |
+ */ |
+ return make_node(AST_Assign, self, { |
+ operator: consequent.operator, |
+ left: consequent.left, |
+ right: make_node(AST_Conditional, self, { |
+ condition: self.condition, |
+ consequent: consequent.right, |
+ alternative: alternative.right |
+ }) |
+ }); |
+ } |
+ if (consequent instanceof AST_Call |
+ && alternative.TYPE === consequent.TYPE |
+ && consequent.args.length == alternative.args.length |
+ && consequent.expression.equivalent_to(alternative.expression)) { |
+ if (consequent.args.length == 0) { |
+ return make_node(AST_Seq, self, { |
+ car: self.condition, |
+ cdr: consequent |
+ }); |
+ } |
+ if (consequent.args.length == 1) { |
+ consequent.args[0] = make_node(AST_Conditional, self, { |
+ condition: self.condition, |
+ consequent: consequent.args[0], |
+ alternative: alternative.args[0] |
+ }); |
+ return consequent; |
+ } |
+ } |
+ // x?y?z:a:a --> x&&y?z:a |
+ if (consequent instanceof AST_Conditional |
+ && consequent.alternative.equivalent_to(alternative)) { |
+ return make_node(AST_Conditional, self, { |
+ condition: make_node(AST_Binary, self, { |
+ left: self.condition, |
+ operator: "&&", |
+ right: consequent.condition |
+ }), |
+ consequent: consequent.consequent, |
+ alternative: alternative |
+ }); |
+ } |
+ // x=y?1:1 --> x=1 |
+ if (consequent instanceof AST_Constant |
+ && alternative instanceof AST_Constant |
+ && consequent.equivalent_to(alternative)) { |
+ if (self.condition.has_side_effects(compressor)) { |
+ return AST_Seq.from_array([self.condition, make_node_from_constant(compressor, consequent.value, self)]); |
+ } else { |
+ return make_node_from_constant(compressor, consequent.value, self); |
+ |
+ } |
+ } |
+ return self; |
+ }); |
+ |
+ OPT(AST_Boolean, function(self, compressor){ |
+ if (compressor.option("booleans")) { |
+ var p = compressor.parent(); |
+ if (p instanceof AST_Binary && (p.operator == "==" |
+ || p.operator == "!=")) { |
+ compressor.warn("Non-strict equality against boolean: {operator} {value} [{file}:{line},{col}]", { |
+ operator : p.operator, |
+ value : self.value, |
+ file : p.start.file, |
+ line : p.start.line, |
+ col : p.start.col, |
+ }); |
+ return make_node(AST_Number, self, { |
+ value: +self.value |
+ }); |
+ } |
+ return make_node(AST_UnaryPrefix, self, { |
+ operator: "!", |
+ expression: make_node(AST_Number, self, { |
+ value: 1 - self.value |
+ }) |
+ }); |
+ } |
+ return self; |
+ }); |
+ |
+ OPT(AST_Sub, function(self, compressor){ |
+ 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)) { |
+ return make_node(AST_Dot, self, { |
+ expression : self.expression, |
+ property : prop |
+ }).optimize(compressor); |
+ } |
+ var v = parseFloat(prop); |
+ if (!isNaN(v) && v.toString() == prop) { |
+ self.property = make_node(AST_Number, self.property, { |
+ value: v |
+ }); |
+ } |
+ } |
+ return self; |
+ }); |
+ |
+ OPT(AST_Dot, function(self, 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); |
+ } |
+ return self.evaluate(compressor)[0]; |
+ }); |
+ |
+ function literals_in_boolean_context(self, compressor) { |
+ if (compressor.option("booleans") && compressor.in_boolean_context()) { |
+ return make_node(AST_True, self); |
+ } |
+ return self; |
+ }; |
+ OPT(AST_Array, literals_in_boolean_context); |
+ OPT(AST_Object, literals_in_boolean_context); |
+ OPT(AST_RegExp, literals_in_boolean_context); |
+ |
+})(); |