| 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);
|
| +
|
| +})();
|
|
|