Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(478)

Unified Diff: node_modules/vulcanize/node_modules/uglify-js/lib/compress.js

Issue 800513006: Added vulcanize under third_party/npm_modules (Closed) Base URL: https://chromium.googlesource.com/infra/third_party/npm_modules.git@master
Patch Set: Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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);
+
+})();

Powered by Google App Engine
This is Rietveld 408576698