ons.slice(1)); 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; }); AST_Scope.DEFMETHOD("var_names", function() { var var_names = this._var_names; if (!var_names) { this._var_names = var_names = Object.create(null); this.enclosed.forEach(function(def) { var_names[def.name] = true; }); this.variables.each(function(def, name) { var_names[name] = true; }); } return var_names; }); AST_Scope.DEFMETHOD("make_var_name", function(prefix) { var var_names = this.var_names(); prefix = prefix.replace(/(?:^[^a-z_$]|[^a-z0-9_$])/ig, "_"); var name = prefix; for (var i = 0; var_names[name]; i++) name = prefix + "$" + i; var_names[name] = true; return name; }); AST_Scope.DEFMETHOD("hoist_properties", function(compressor){ var self = this; if (!compressor.option("hoist_props") || compressor.has_directive("use asm")) return self; var top_retain = self instanceof AST_Toplevel && compressor.top_retain || return_false; var defs_by_id = Object.create(null); return self.transform(new TreeTransformer(function(node, descend) { if (node instanceof AST_VarDef) { var sym = node.name, def, value; if (sym.scope === self && (def = sym.definition()).escaped != 1 && !def.assignments && !def.direct_access && !def.single_use && !top_retain(def) && (value = sym.fixed_value()) === node.value && value instanceof AST_Object) { descend(node, this); var defs = new Dictionary(); var assignments = []; value.properties.forEach(function(prop) { assignments.push(make_node(AST_VarDef, node, { name: make_sym(prop.key), value: prop.value })); }); defs_by_id[def.id] = defs; return MAP.splice(assignments); } } if (node instanceof AST_PropAccess && node.expression instanceof AST_SymbolRef) { var defs = defs_by_id[node.expression.definition().id]; if (defs) { var def = defs.get(get_value(node.property)); var sym = make_node(AST_SymbolRef, node, { name: def.name, scope: node.expression.scope, thedef: def }); sym.reference({}); return sym; } } function make_sym(key) { var new_var = make_node(sym.CTOR, sym, { name: self.make_var_name(sym.name + "_" + key), scope: self }); var def = self.def_variable(new_var); defs.set(key, def); self.enclosed.push(def); return new_var; } })); }); // drop_side_effect_free() // remove side-effect-free parts which only affects return value (function(def){ // Drop side-effect-free elements from an array of expressions. // Returns an array of expressions with side-effects or null // if all elements were dropped. Note: original array may be // returned if nothing changed. function trim(nodes, compressor, first_in_statement) { var len = nodes.length; if (!len) return null; var ret = [], changed = false; for (var i = 0; i < len; i++) { var node = nodes[i].drop_side_effect_free(compressor, first_in_statement); changed |= node !== nodes[i]; if (node) { ret.push(node); first_in_statement = false; } } return changed ? ret.length ? ret : null : nodes; } def(AST_Node, return_this); def(AST_Constant, return_null); def(AST_This, return_null); def(AST_Call, function(compressor, first_in_statement){ if (!this.is_expr_pure(compressor)) { if (this.expression.is_call_pure(compressor)) { var exprs = this.args.slice(); exprs.unshift(this.expression.expression); exprs = trim(exprs, compressor, first_in_statement); return exprs && make_sequence(this, exprs); } if (this.expression instanceof AST_Function && (!this.expression.name || !this.expression.name.definition().references.length)) { var node = this.clone(); var exp = node.expression; exp.process_expression(false, compressor); exp.walk(new TreeWalker(function(node) { if (node instanceof AST_Return && node.value) { node.value = node.value.drop_side_effect_free(compressor); return true; } if (node instanceof AST_Scope && node !== exp) return true; })); return node; } return this; } if (this.pure) { compressor.warn("Dropping __PURE__ call [{file}:{line},{col}]", this.start); } var args = trim(this.args, compressor, first_in_statement); return args && make_sequence(this, args); }); def(AST_Accessor, return_null); def(AST_Function, return_null); def(AST_Binary, function(compressor, first_in_statement){ var right = this.right.drop_side_effect_free(compressor); if (!right) return this.left.drop_side_effect_free(compressor, first_in_statement); if (lazy_op[this.operator]) { if (right === this.right) return this; var node = this.clone(); node.right = right; return node; } else { var left = this.left.drop_side_effect_free(compressor, first_in_statement); if (!left) return this.right.drop_side_effect_free(compressor, first_in_statement); return make_sequence(this, [ left, right ]); } }); def(AST_Assign, function(compressor){ var left = this.left; if (left.has_side_effects(compressor) || compressor.has_directive("use strict") && left instanceof AST_PropAccess && left.expression.is_constant()) { return this; } this.write_only = true; if (root_expr(left).is_constant_expression(compressor.find_parent(AST_Scope))) { return this.right.drop_side_effect_free(compressor); } return this; }); def(AST_Conditional, function(compressor){ var consequent = this.consequent.drop_side_effect_free(compressor); var alternative = this.alternative.drop_side_effect_free(compressor); if (consequent === this.consequent && alternative === this.alternative) return this; if (!consequent) return alternative ? make_node(AST_Binary, this, { operator: "||", left: this.condition, right: alternative }) : this.condition.drop_side_effect_free(compressor); if (!alternative) return make_node(AST_Binary, this, { operator: "&&", left: this.condition, right: consequent }); var node = this.clone(); node.consequent = consequent; node.alternative = alternative; return node; }); def(AST_Unary, function(compressor, first_in_statement){ if (unary_side_effects[this.operator]) { this.write_only = !this.expression.has_side_effects(compressor); return this; } if (this.operator == "typeof" && this.expression instanceof AST_SymbolRef) return null; var expression = this.expression.drop_side_effect_free(compressor, first_in_statement); if (first_in_statement && expression && is_iife_call(expression)) { if (expression === this.expression && this.operator == "!") return this; return expression.negate(compressor, first_in_statement); } return expression; }); def(AST_SymbolRef, function(compressor) { return this.is_declared(compressor) ? null : this; }); def(AST_Object, function(compressor, first_in_statement){ var values = trim(this.properties, compressor, first_in_statement); return values && make_sequence(this, values); }); def(AST_ObjectProperty, function(compressor, first_in_statement){ return this.value.drop_side_effect_free(compressor, first_in_statement); }); def(AST_Array, function(compressor, first_in_statement){ var values = trim(this.elements, compressor, first_in_statement); return values && make_sequence(this, values); }); def(AST_Dot, function(compressor, first_in_statement){ if (this.expression.may_throw_on_access(compressor)) return this; return this.expression.drop_side_effect_free(compressor, first_in_statement); }); def(AST_Sub, function(compressor, first_in_statement){ if (this.expression.may_throw_on_access(compressor)) return this; var expression = this.expression.drop_side_effect_free(compressor, first_in_statement); if (!expression) return this.property.drop_side_effect_free(compressor, first_in_statement); var property = this.property.drop_side_effect_free(compressor); if (!property) return expression; return make_sequence(this, [ expression, property ]); }); def(AST_Sequence, function(compressor){ var last = this.tail_node(); var expr = last.drop_side_effect_free(compressor); if (expr === last) return this; var expressions = this.expressions.slice(0, -1); if (expr) expressions.push(expr); return make_sequence(this, expressions); }); })(function(node, func){ node.DEFMETHOD("drop_side_effect_free", func); }); OPT(AST_SimpleStatement, function(self, compressor){ if (compressor.option("side_effects")) { var body = self.body; var node = body.drop_side_effect_free(compressor, true); if (!node) { compressor.warn("Dropping side-effect-free statement [{file}:{line},{col}]", self.start); return make_node(AST_EmptyStatement, self); } if (node !== body) { return make_node(AST_SimpleStatement, self, { body: node }); } } return self; }); OPT(AST_While, function(self, compressor){ return compressor.option("loops") ? make_node(AST_For, self, self).optimize(compressor) : self; }); OPT(AST_Do, function(self, compressor){ if (!compressor.option("loops")) return self; var cond = self.condition.is_truthy() || self.condition.tail_node().evaluate(compressor); if (!(cond instanceof AST_Node)) { if (cond) return make_node(AST_For, self, { body: make_node(AST_BlockStatement, self.body, { body: [ self.body, make_node(AST_SimpleStatement, self.condition, { body: self.condition }) ] }) }).optimize(compressor); var has_loop_control = false; var tw = new TreeWalker(function(node) { if (node instanceof AST_Scope || has_loop_control) return true; if (node instanceof AST_LoopControl && tw.loopcontrol_target(node) === self) return has_loop_control = true; }); var parent = compressor.parent(); (parent instanceof AST_LabeledStatement ? parent : self).walk(tw); if (!has_loop_control) return make_node(AST_BlockStatement, self.body, { body: [ self.body, make_node(AST_SimpleStatement, self.condition, { body: self.condition }) ] }).optimize(compressor); } if (self.body instanceof AST_SimpleStatement) return make_node(AST_For, self, { condition: make_sequence(self.condition, [ self.body.body, self.condition ]), body: make_node(AST_EmptyStatement, self) }).optimize(compressor); return self; }); function if_break_in_loop(self, compressor) { var first = self.body instanceof AST_BlockStatement ? self.body.body[0] : self.body; if (compressor.option("dead_code") && is_break(first)) { var body = []; if (self.init instanceof AST_Statement) { body.push(self.init); } else if (self.init) { body.push(make_node(AST_SimpleStatement, self.init, { body: self.init })); } if (self.condition) { body.push(make_node(AST_SimpleStatement, self.condition, { body: self.condition })); } extract_declarations_from_unreachable_code(compressor, self.body, body); return make_node(AST_BlockStatement, self, { body: body }); } if (first instanceof AST_If) { if (is_break(first.body)) { 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 (is_break(first.alternative)) { 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); } } return self; function is_break(node) { return node instanceof AST_Break && compressor.loopcontrol_target(node) === compressor.self(); } 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); } self = if_break_in_loop(self, compressor); } } OPT(AST_For, function(self, compressor){ if (!compressor.option("loops")) return self; if (compressor.option("side_effects") && self.init) { self.init = self.init.drop_side_effect_free(compressor); } if (self.condition) { var cond = self.condition.evaluate(compressor); if (!(cond instanceof AST_Node)) { if (cond) self.condition = null; else if (!compressor.option("dead_code")) { var orig = self.condition; self.condition = make_node_from_constant(cond, self.condition); self.condition = best_of_expression(self.condition.transform(compressor), orig); } } if (cond instanceof AST_Node) { cond = self.condition.is_truthy() || self.condition.tail_node().evaluate(compressor); } if (!cond) { if (compressor.option("dead_code")) { var body = []; extract_declarations_from_unreachable_code(compressor, self.body, body); if (self.init instanceof AST_Statement) { body.push(self.init); } else if (self.init) { body.push(make_node(AST_SimpleStatement, self.init, { body: self.init })); } body.push(make_node(AST_SimpleStatement, self.condition, { body: self.condition })); return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor); } } else if (self.condition && !(cond instanceof AST_Node)) { self.body = make_node(AST_BlockStatement, self.body, { body: [ make_node(AST_SimpleStatement, self.condition, { body: self.condition }), self.body ] }); self.condition = null; } } return if_break_in_loop(self, compressor); }); OPT(AST_If, function(self, compressor){ if (is_empty(self.alternative)) self.alternative = null; 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); if (!compressor.option("dead_code") && !(cond instanceof AST_Node)) { var orig = self.condition; self.condition = make_node_from_constant(cond, orig); self.condition = best_of_expression(self.condition.transform(compressor), orig); } if (compressor.option("dead_code")) { if (cond instanceof AST_Node) { cond = self.condition.is_truthy() || self.condition.tail_node().evaluate(compressor); } if (!cond) { compressor.warn("Condition always false [{file}:{line},{col}]", self.condition.start); var body = []; extract_declarations_from_unreachable_code(compressor, self.body, body); body.push(make_node(AST_SimpleStatement, self.condition, { body: self.condition })); if (self.alternative) body.push(self.alternative); return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor); } else if (!(cond instanceof AST_Node)) { compressor.warn("Condition always true [{file}:{line},{col}]", self.condition.start); var body = []; if (self.alternative) { extract_declarations_from_unreachable_code(compressor, self.alternative, body); } body.push(make_node(AST_SimpleStatement, self.condition, { body: self.condition })); body.push(self.body); return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor); } } var negated = self.condition.negate(compressor); var self_condition_length = self.condition.print_to_string().length; var negated_length = negated.print_to_string().length; var negated_is_best = negated_length < self_condition_length; if (self.alternative && negated_is_best) { negated_is_best = false; // because we already do the switch here. // no need to swap values of self_condition_length and negated_length // here because they are only used in an equality comparison later on. self.condition = negated; var tmp = self.body; self.body = self.alternative || make_node(AST_EmptyStatement, self); self.alternative = tmp; } if (is_empty(self.body) && is_empty(self.alternative)) { return make_node(AST_SimpleStatement, self.condition, { body: self.condition.clone() }).optimize(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 }) }).optimize(compressor); } if (is_empty(self.alternative) && self.body instanceof AST_SimpleStatement) { if (self_condition_length === negated_length && !negated_is_best && self.condition instanceof AST_Binary && self.condition.operator == "||") { // although the code length of self.condition and negated are the same, // negated does not require additional surrounding parentheses. // see https://github.com/mishoo/UglifyJS2/issues/979 negated_is_best = true; } if (negated_is_best) return make_node(AST_SimpleStatement, self, { body: make_node(AST_Binary, self, { operator : "||", left : negated, right : self.body.body }) }).optimize(compressor); return make_node(AST_SimpleStatement, self, { body: make_node(AST_Binary, self, { operator : "&&", left : self.condition, right : self.body.body }) }).optimize(compressor); } if (self.body instanceof AST_EmptyStatement && 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 }) }).optimize(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), alternative : self.alternative.value || make_node(AST_Undefined, self.alternative) }).transform(compressor) }).optimize(compressor); } if (self.body instanceof AST_If && !self.body.alternative && !self.alternative) { self = make_node(AST_If, self, { condition: make_node(AST_Binary, self.condition, { operator: "&&", left: self.condition, right: self.body.condition }), body: self.body.body, alternative: null }); } if (aborts(self.body)) { if (self.alternative) { var alt = self.alternative; self.alternative = null; return make_node(AST_BlockStatement, self, { body: [ self, alt ] }).optimize(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 ] }).optimize(compressor); } return self; }); OPT(AST_Switch, function(self, compressor){ if (!compressor.option("switches")) return self; var branch; var value = self.expression.evaluate(compressor); if (!(value instanceof AST_Node)) { var orig = self.expression; self.expression = make_node_from_constant(value, orig); self.expression = best_of_expression(self.expression.transform(compressor), orig); } if (!compressor.option("dead_code")) return self; if (value instanceof AST_Node) { value = self.expression.tail_node().evaluate(compressor); } var decl = []; var body = []; var default_branch; var exact_match; for (var i = 0, len = self.body.length; i < len && !exact_match; i++) { branch = self.body[i]; if (branch instanceof AST_Default) { if (!default_branch) { default_branch = branch; } else { eliminate_branch(branch, body[body.length - 1]); } } else if (!(value instanceof AST_Node)) { var exp = branch.expression.evaluate(compressor); if (!(exp instanceof AST_Node) && exp !== value) { eliminate_branch(branch, body[body.length - 1]); continue; } if (exp instanceof AST_Node) exp = branch.expression.tail_node().evaluate(compressor); if (exp === value) { exact_match = branch; if (default_branch) { var default_index = body.indexOf(default_branch); body.splice(default_index, 1); eliminate_branch(default_branch, body[default_index - 1]); default_branch = null; } } } if (aborts(branch)) { var prev = body[body.length - 1]; if (aborts(prev) && prev.body.length == branch.body.length && make_node(AST_BlockStatement, prev, prev).equivalent_to(make_node(AST_BlockStatement, branch, branch))) { prev.body = []; } } body.push(branch); } while (i < len) eliminate_branch(self.body[i++], body[body.length - 1]); if (body.length > 0) { body[0].body = decl.concat(body[0].body); } self.body = body; while (branch = body[body.length - 1]) { var stat = branch.body[branch.body.length - 1]; if (stat instanceof AST_Break && compressor.loopcontrol_target(stat) === self) branch.body.pop(); if (branch.body.length || branch instanceof AST_Case && (default_branch || branch.expression.has_side_effects(compressor))) break; if (body.pop() === default_branch) default_branch = null; } if (body.length == 0) { return make_node(AST_BlockStatement, self, { body: decl.concat(make_node(AST_SimpleStatement, self.expression, { body: self.expression })) }).optimize(compressor); } if (body.length == 1 && (body[0] === exact_match || body[0] === default_branch)) { var has_break = false; var tw = new TreeWalker(function(node) { if (has_break || node instanceof AST_Lambda || node instanceof AST_SimpleStatement) return true; if (node instanceof AST_Break && tw.loopcontrol_target(node) === self) has_break = true; }); self.walk(tw); if (!has_break) { var statements = body[0].body.slice(); var exp = body[0].expression; if (exp) statements.unshift(make_node(AST_SimpleStatement, exp, { body: exp })); statements.unshift(make_node(AST_SimpleStatement, self.expression, { body:self.expression })); return make_node(AST_BlockStatement, self, { body: statements }).optimize(compressor); } } return self; function eliminate_branch(branch, prev) { if (prev && !aborts(prev)) { prev.body = prev.body.concat(branch.body); } else { extract_declarations_from_unreachable_code(compressor, branch, decl); } } }); OPT(AST_Try, function(self, compressor){ tighten_body(self.body, compressor); if (self.bcatch && self.bfinally && all(self.bfinally.body, is_empty)) self.bfinally = null; if (compressor.option("dead_code") && all(self.body, is_empty)) { var body = []; if (self.bcatch) { extract_declarations_from_unreachable_code(compressor, self.bcatch, body); body.forEach(function(stat) { if (!(stat instanceof AST_Definitions)) return; stat.definitions.forEach(function(var_def) { var def = var_def.name.definition().redefined(); if (!def) return; var_def.name = var_def.name.clone(); var_def.name.thedef = def; }); }); } if (self.bfinally) body = body.concat(self.bfinally.body); return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor); } return self; }); AST_Definitions.DEFMETHOD("remove_initializers", function(){ this.definitions.forEach(function(def){ def.value = null }); }); AST_Definitions.DEFMETHOD("to_assignments", function(compressor){ var reduce_vars = compressor.option("reduce_vars"); 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 })); if (reduce_vars) name.definition().fixed = false; } def = def.name.definition(); def.eliminated++; def.replaced--; return a; }, []); if (assignments.length == 0) return null; return make_sequence(this, assignments); }); OPT(AST_Definitions, function(self, compressor){ if (self.definitions.length == 0) return make_node(AST_EmptyStatement, self); return self; }); OPT(AST_Call, function(self, compressor){ var exp = self.expression; var fn = exp; if (compressor.option("reduce_vars") && fn instanceof AST_SymbolRef) { fn = fn.fixed_value(); } var is_func = fn instanceof AST_Lambda; if (compressor.option("unused") && is_func && !fn.uses_arguments && !fn.uses_eval) { var pos = 0, last = 0; for (var i = 0, len = self.args.length; i < len; i++) { var trim = i >= fn.argnames.length; if (trim || fn.argnames[i].__unused) { var node = self.args[i].drop_side_effect_free(compressor); if (node) { self.args[pos++] = node; } else if (!trim) { self.args[pos++] = make_node(AST_Number, self.args[i], { value: 0 }); continue; } } else { self.args[pos++] = self.args[i]; } last = pos; } self.args.length = last; } if (compressor.option("unsafe")) { if (is_undeclared_ref(exp)) switch (exp.name) { case "Array": if (self.args.length != 1) { return make_node(AST_Array, self, { elements: self.args }).optimize(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: "" }) }).optimize(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: "+" }).optimize(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, self, { expression: self.args[0], operator: "!" }), operator: "!" }).optimize(compressor); break; case "RegExp": var params = []; if (all(self.args, function(arg) { var value = arg.evaluate(compressor); params.unshift(value); return arg !== value; })) { try { return best_of(compressor, self, make_node(AST_RegExp, self, { value: RegExp.apply(RegExp, params), })); } catch (ex) { compressor.warn("Error converting {expr} [{file}:{line},{col}]", { expr: self.print_to_string(), file: self.start.file, line: self.start.line, col: self.start.col }); } } break; } else if (exp instanceof AST_Dot) switch(exp.property) { case "toString": if (self.args.length == 0 && !exp.expression.may_throw_on_access(compressor)) { return make_node(AST_Binary, self, { left: make_node(AST_String, self, { value: "" }), operator: "+", right: exp.expression }).optimize(compressor); } break; case "join": if (exp.expression instanceof AST_Array) EXIT: { var separator; if (self.args.length > 0) { separator = self.args[0].evaluate(compressor); if (separator === self.args[0]) break EXIT; // not a constant } var elements = []; var consts = []; exp.expression.elements.forEach(function(el) { var value = el.evaluate(compressor); if (value !== el) { consts.push(value); } else { if (consts.length > 0) { elements.push(make_node(AST_String, self, { value: consts.join(separator) })); consts.length = 0; } elements.push(el); } }); if (consts.length > 0) { elements.push(make_node(AST_String, self, { value: consts.join(separator) })); } if (elements.length == 0) return make_node(AST_String, self, { value: "" }); if (elements.length == 1) { if (elements[0].is_string(compressor)) { return elements[0]; } return make_node(AST_Binary, elements[0], { operator : "+", left : make_node(AST_String, self, { value: "" }), right : elements[0] }); } if (separator == "") { var first; if (elements[0].is_string(compressor) || elements[1].is_string(compressor)) { first = elements.shift(); } else { first = make_node(AST_String, self, { value: "" }); } return elements.reduce(function(prev, el){ return make_node(AST_Binary, el, { operator : "+", left : prev, right : el }); }, first).optimize(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; return best_of(compressor, self, node); } break; case "charAt": if (exp.expression.is_string(compressor)) { var arg = self.args[0]; var index = arg ? arg.evaluate(compressor) : 0; if (index !== arg) { return make_node(AST_Sub, exp, { expression: exp.expression, property: make_node_from_constant(index | 0, arg || exp) }).optimize(compressor); } } break; case "apply": if (self.args.length == 2 && self.args[1] instanceof AST_Array) { var args = self.args[1].elements.slice(); args.unshift(self.args[0]); return make_node(AST_Call, self, { expression: make_node(AST_Dot, exp, { expression: exp.expression, property: "call" }), args: args }).optimize(compressor); } break; case "call": var func = exp.expression; if (func instanceof AST_SymbolRef) { func = func.fixed_value(); } if (func instanceof AST_Lambda && !func.contains_this()) { return make_sequence(this, [ self.args[0], make_node(AST_Call, self, { expression: exp.expression, args: self.args.slice(1) }) ]).optimize(compressor); } break; } } if (compressor.option("unsafe_Function") && is_undeclared_ref(exp) && exp.name == "Function") { // new Function() => function(){} if (self.args.length == 0) return make_node(AST_Function, self, { argnames: [], body: [] }); if (all(self.args, function(x) { return x instanceof AST_String; })) { // quite a corner-case, but we can handle it: // https://github.com/mishoo/UglifyJS2/issues/203 // if the code argument is a constant, then we can minify it. try { var code = "n(function(" + self.args.slice(0, -1).map(function(arg) { return arg.value; }).join(",") + "){" + self.args[self.args.length - 1].value + "})"; var ast = parse(code); var mangle = { ie8: compressor.option("ie8") }; ast.figure_out_scope(mangle); var comp = new Compressor(compressor.options); ast = ast.transform(comp); ast.figure_out_scope(mangle); ast.compute_char_frequency(mangle); ast.mangle_names(mangle); var fun; ast.walk(new TreeWalker(function(node) { if (fun) return true; if (node instanceof AST_Lambda) { fun = node; return true; } })); var code = OutputStream(); AST_BlockStatement.prototype._codegen.call(fun, fun, code); self.args = [ make_node(AST_String, self, { value: fun.argnames.map(function(arg) { return arg.print_to_string(); }).join(",") }), make_node(AST_String, self.args[self.args.length - 1], { value: code.get().replace(/^\{|\}$/g, "") }) ]; return self; } catch (ex) { if (ex instanceof JS_Parse_Error) { compressor.warn("Error parsing code passed to new Function [{file}:{line},{col}]", self.args[self.args.length - 1].start); compressor.warn(ex.toString()); } else { throw ex; } } } } var stat = is_func && fn.body[0]; if (compressor.option("inline") && stat instanceof AST_Return) { var value = stat.value; if (!value || value.is_constant_expression()) { if (value) { value = value.clone(true); } else { value = make_node(AST_Undefined, self); } var args = self.args.concat(value); return make_sequence(self, args).optimize(compressor); } } if (is_func) { var def, value, scope, in_loop, level = -1; if (compressor.option("inline") && !fn.uses_arguments && !fn.uses_eval && !(fn.name && fn instanceof AST_Function) && (value = can_flatten_body(stat)) && (exp === fn || compressor.option("unused") && (def = exp.definition()).references.length == 1 && !recursive_ref(compressor, def) && fn.is_constant_expression(exp.scope)) && !self.pure && !fn.contains_this() && can_inject_symbols()) { fn._squeezed = true; return make_sequence(self, flatten_fn()).optimize(compressor); } if (compressor.option("side_effects") && all(fn.body, is_empty)) { var args = self.args.concat(make_node(AST_Undefined, self)); return make_sequence(self, args).optimize(compressor); } } if (compressor.option("drop_console")) { if (exp instanceof AST_PropAccess) { var name = exp.expression; while (name.expression) { name = name.expression; } if (is_undeclared_ref(name) && name.name == "console") { return make_node(AST_Undefined, self).optimize(compressor); } } } if (compressor.option("negate_iife") && compressor.parent() instanceof AST_SimpleStatement && is_iife_call(self)) { return self.negate(compressor, true); } var ev = self.evaluate(compressor); if (ev !== self) { ev = make_node_from_constant(ev, self).optimize(compressor); return best_of(compressor, ev, self); } return self; function return_value(stat) { if (!stat) return make_node(AST_Undefined, self); if (stat instanceof AST_Return) { if (!stat.value) return make_node(AST_Undefined, self); return stat.value.clone(true); } if (stat instanceof AST_SimpleStatement) { return make_node(AST_UnaryPrefix, stat, { operator: "void", expression: stat.body.clone(true) }); } } function can_flatten_body(stat) { var len = fn.body.length; if (compressor.option("inline") < 3) { return len == 1 && return_value(stat); } stat = null; for (var i = 0; i < len; i++) { var line = fn.body[i]; if (line instanceof AST_Var) { if (stat && !all(line.definitions, function(var_def) { return !var_def.value; })) { return false; } } else if (line instanceof AST_EmptyStatement) { continue; } else if (stat) { return false; } else { stat = line; } } return return_value(stat); } function can_inject_args(catches, safe_to_inject) { for (var i = 0, len = fn.argnames.length; i < len; i++) { var arg = fn.argnames[i]; if (arg.__unused) continue; if (!safe_to_inject || catches[arg.name] || identifier_atom[arg.name] || scope.var_names()[arg.name]) { return false; } if (in_loop) in_loop.push(arg.definition()); } return true; } function can_inject_vars(catches, safe_to_inject) { var len = fn.body.length; for (var i = 0; i < len; i++) { var stat = fn.body[i]; if (!(stat instanceof AST_Var)) continue; if (!safe_to_inject) return false; for (var j = stat.definitions.length; --j >= 0;) { var name = stat.definitions[j].name; if (catches[name.name] || identifier_atom[name.name] || scope.var_names()[name.name]) { return false; } if (in_loop) in_loop.push(name.definition()); } } return true; } function can_inject_symbols() { var catches = Object.create(null); do { scope = compressor.parent(++level); if (scope instanceof AST_Catch) { catches[scope.argname.name] = true; } else if (scope instanceof AST_IterationStatement) { in_loop = []; } else if (scope instanceof AST_SymbolRef) { if (scope.fixed_value() instanceof AST_Scope) return false; } } while (!(scope instanceof AST_Scope)); var safe_to_inject = !(scope instanceof AST_Toplevel) || compressor.toplevel.vars; var inline = compressor.option("inline"); if (!can_inject_vars(catches, inline >= 3 && safe_to_inject)) return false; if (!can_inject_args(catches, inline >= 2 && safe_to_inject)) return false; return !in_loop || in_loop.length == 0 || !is_reachable(fn, in_loop); } function append_var(decls, expressions, name, value) { var def = name.definition(); scope.variables.set(name.name, def); scope.enclosed.push(def); if (!scope.var_names()[name.name]) { scope.var_names()[name.name] = true; decls.push(make_node(AST_VarDef, name, { name: name, value: null })); } var sym = make_node(AST_SymbolRef, name, name); def.references.push(sym); if (value) expressions.push(make_node(AST_Assign, self, { operator: "=", left: sym, right: value })); } function flatten_args(decls, expressions) { var len = fn.argnames.length; for (var i = self.args.length; --i >= len;) { expressions.push(self.args[i]); } for (i = len; --i >= 0;) { var name = fn.argnames[i]; var value = self.args[i]; if (name.__unused || scope.var_names()[name.name]) { if (value) expressions.push(value); } else { var symbol = make_node(AST_SymbolVar, name, name); name.definition().orig.push(symbol); if (!value && in_loop) value = make_node(AST_Undefined, self); append_var(decls, expressions, symbol, value); } } decls.reverse(); expressions.reverse(); } function flatten_vars(decls, expressions) { var pos = expressions.length; for (var i = 0, lines = fn.body.length; i < lines; i++) { var stat = fn.body[i]; if (!(stat instanceof AST_Var)) continue; for (var j = 0, defs = stat.definitions.length; j < defs; j++) { var var_def = stat.definitions[j]; var name = var_def.name; append_var(decls, expressions, name, var_def.value); if (in_loop && all(fn.argnames, function(argname) { return argname.name != name.name; })) { var def = fn.variables.get(name.name); var sym = make_node(AST_SymbolRef, name, name); def.references.push(sym); expressions.splice(pos++, 0, make_node(AST_Assign, var_def, { operator: "=", left: sym, right: make_node(AST_Undefined, name) })); } } } } function flatten_fn() { var decls = []; var expressions = []; flatten_args(decls, expressions); flatten_vars(decls, expressions); expressions.push(value); if (decls.length) { i = scope.body.indexOf(compressor.parent(level - 1)) + 1; scope.body.splice(i, 0, make_node(AST_Var, fn, { definitions: decls })); } return expressions; } }); OPT(AST_New, function(self, compressor){ if (compressor.option("unsafe")) { var exp = self.expression; if (is_undeclared_ref(exp)) { 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_Sequence, function(self, compressor){ if (!compressor.option("side_effects")) return self; var expressions = []; filter_for_side_effects(); var end = expressions.length - 1; trim_right_for_undefined(); if (end == 0) { self = maintain_this_binding(compressor.parent(), compressor.self(), expressions[0]); if (!(self instanceof AST_Sequence)) self = self.optimize(compressor); return self; } self.expressions = expressions; return self; function filter_for_side_effects() { var first = first_in_statement(compressor); var last = self.expressions.length - 1; self.expressions.forEach(function(expr, index) { if (index < last) expr = expr.drop_side_effect_free(compressor, first); if (expr) { merge_sequence(expressions, expr); first = false; } }); } function trim_right_for_undefined() { while (end > 0 && is_undefined(expressions[end], compressor)) end--; if (end < expressions.length - 1) { expressions[end] = make_node(AST_UnaryPrefix, self, { operator : "void", expression : expressions[end] }); expressions.length = end + 1; } } }); AST_Unary.DEFMETHOD("lift_sequences", function(compressor){ if (compressor.option("sequences")) { if (this.expression instanceof AST_Sequence) { var x = this.expression.expressions.slice(); var e = this.clone(); e.expression = x.pop(); x.push(e); return make_sequence(this, x).optimize(compressor); } } return this; }); OPT(AST_UnaryPostfix, function(self, compressor){ return self.lift_sequences(compressor); }); OPT(AST_UnaryPrefix, function(self, compressor){ var e = self.expression; if (self.operator == "delete" && !(e instanceof AST_SymbolRef || e instanceof AST_PropAccess || is_identifier_atom(e))) { if (e instanceof AST_Sequence) { e = e.expressions.slice(); e.push(make_node(AST_True, self)); return make_sequence(self, e).optimize(compressor); } return make_sequence(self, [ e, make_node(AST_True, self) ]).optimize(compressor); } var seq = self.lift_sequences(compressor); if (seq !== self) { return seq; } if (compressor.option("side_effects") && self.operator == "void") { e = e.drop_side_effect_free(compressor); if (e) { self.expression = e; return self; } else { return make_node(AST_Undefined, self).optimize(compressor); } } if (compressor.option("booleans")) { if (self.operator == "!" && e.is_truthy()) { return make_sequence(self, [ e, make_node(AST_False, self) ]).optimize(compressor); } else if (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; } if (e instanceof AST_Binary) { self = best_of(compressor, self, e.negate(compressor, first_in_statement(compressor))); } 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 (e instanceof AST_SymbolRef ? make_node(AST_True, self) : make_sequence(self, [ e, make_node(AST_True, self) ])).optimize(compressor); } } if (self.operator == "-" && e instanceof AST_Infinity) { e = e.transform(compressor); } if (e instanceof AST_Binary && (self.operator == "+" || self.operator == "-") && (e.operator == "*" || e.operator == "/" || e.operator == "%")) { return make_node(AST_Binary, self, { operator: e.operator, left: make_node(AST_UnaryPrefix, e.left, { operator: self.operator, expression: e.left }), right: e.right }); } // avoids infinite recursion of numerals if (self.operator != "-" || !(e instanceof AST_Number || e instanceof AST_Infinity)) { var ev = self.evaluate(compressor); if (ev !== self) { ev = make_node_from_constant(ev, self).optimize(compressor); return best_of(compressor, ev, self); } } return self; }); AST_Binary.DEFMETHOD("lift_sequences", function(compressor){ if (compressor.option("sequences")) { if (this.left instanceof AST_Sequence) { var x = this.left.expressions.slice(); var e = this.clone(); e.left = x.pop(); x.push(e); return make_sequence(this, x).optimize(compressor); } if (this.right instanceof AST_Sequence && !this.left.has_side_effects(compressor)) { var assign = this.operator == "=" && this.left instanceof AST_SymbolRef; var x = this.right.expressions; var last = x.length - 1; for (var i = 0; i < last; i++) { if (!assign && x[i].has_side_effects(compressor)) break; } if (i == last) { x = x.slice(); var e = this.clone(); e.right = x.pop(); x.push(e); return make_sequence(this, x).optimize(compressor); } else if (i > 0) { var e = this.clone(); e.right = make_sequence(this.right, x.slice(i)); x = x.slice(0, i); x.push(e); return make_sequence(this, x).optimize(compressor); } } } return this; }); var commutativeOperators = makePredicate("== === != !== * & | ^"); function is_object(node) { return node instanceof AST_Array || node instanceof AST_Lambda || node instanceof AST_Object; } OPT(AST_Binary, function(self, compressor){ function reversible() { return self.left.is_constant() || self.right.is_constant() || !self.left.has_side_effects(compressor) && !self.right.has_side_effects(compressor); } function reverse(op) { if (reversible()) { if (op) self.operator = op; var tmp = self.left; self.left = self.right; self.right = tmp; } } if (commutativeOperators[self.operator]) { if (self.right.is_constant() && !self.left.is_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(); } } } self = self.lift_sequences(compressor); if (compressor.option("comparisons")) switch (self.operator) { case "===": case "!==": var is_strict_comparison = true; if ((self.left.is_string(compressor) && self.right.is_string(compressor)) || (self.left.is_number(compressor) && self.right.is_number(compressor)) || (self.left.is_boolean() && self.right.is_boolean()) || self.left.equivalent_to(self.right)) { self.operator = self.operator.substr(0, 2); } // XXX: intentionally falling down to the next case case "==": case "!=": // void 0 == x => null == x if (!is_strict_comparison && is_undefined(self.left, compressor)) { self.left = make_node(AST_Null, self.left); } // "undefined" == typeof x => undefined === x else if (compressor.option("typeofs") && self.left instanceof AST_String && self.left.value == "undefined" && self.right instanceof AST_UnaryPrefix && self.right.operator == "typeof") { var expr = self.right.expression; if (expr instanceof AST_SymbolRef ? expr.is_declared(compressor) : !(expr instanceof AST_PropAccess && compressor.option("ie8"))) { self.right = expr; self.left = make_node(AST_Undefined, self.left).optimize(compressor); if (self.operator.length == 2) self.operator += "="; } } // obj !== obj => false else if (self.left instanceof AST_SymbolRef && self.right instanceof AST_SymbolRef && self.left.definition() === self.right.definition() && is_object(self.left.fixed_value())) { return make_node(self.operator[0] == "=" ? AST_True : AST_False, self); } break; case "&&": case "||": var lhs = self.left; if (lhs.operator == self.operator) { lhs = lhs.right; } if (lhs instanceof AST_Binary && lhs.operator == (self.operator == "&&" ? "!==" : "===") && self.right instanceof AST_Binary && lhs.operator == self.right.operator && (is_undefined(lhs.left, compressor) && self.right.left instanceof AST_Null || lhs.left instanceof AST_Null && is_undefined(self.right.left, compressor)) && !lhs.right.has_side_effects(compressor) && lhs.right.equivalent_to(self.right.right)) { var combined = make_node(AST_Binary, self, { operator: lhs.operator.slice(0, -1), left: make_node(AST_Null, self), right: lhs.right }); if (lhs !== self.left) { combined = make_node(AST_Binary, self, { operator: self.operator, left: self.left.left, right: combined }); } return combined; } break; } if (compressor.option("booleans") && self.operator == "+" && compressor.in_boolean_context()) { var ll = self.left.evaluate(compressor); var rr = self.right.evaluate(compressor); if (ll && typeof ll == "string") { compressor.warn("+ in boolean context always true [{file}:{line},{col}]", self.start); return make_sequence(self, [ self.right, make_node(AST_True, self) ]).optimize(compressor); } if (rr && typeof rr == "string") { compressor.warn("+ in boolean context always true [{file}:{line},{col}]", self.start); return make_sequence(self, [ self.left, make_node(AST_True, self) ]).optimize(compressor); } } if (compressor.option("comparisons") && self.is_boolean()) { if (!(compressor.parent() instanceof AST_Binary) || compressor.parent() instanceof AST_Assign) { var negated = make_node(AST_UnaryPrefix, self, { operator: "!", expression: self.negate(compressor, first_in_statement(compressor)) }); self = best_of(compressor, self, negated); } switch (self.operator) { case ">": reverse("<"); break; case ">=": reverse("<="); break; } } if (self.operator == "+") { if (self.right instanceof AST_String && self.right.getValue() == "" && self.left.is_string(compressor)) { return self.left; } if (self.left instanceof AST_String && self.left.getValue() == "" && self.right.is_string(compressor)) { return self.right; } if (self.left instanceof AST_Binary && self.left.operator == "+" && self.left.left instanceof AST_String && self.left.left.getValue() == "" && self.right.is_string(compressor)) { self.left = self.left.right; return self.transform(compressor); } } if (compressor.option("evaluate")) { switch (self.operator) { case "&&": var ll = fuzzy_eval(self.left); if (!ll) { compressor.warn("Condition left of && always false [{file}:{line},{col}]", self.start); return maintain_this_binding(compressor.parent(), compressor.self(), self.left).optimize(compressor); } else if (!(ll instanceof AST_Node)) { compressor.warn("Condition left of && always true [{file}:{line},{col}]", self.start); return make_sequence(self, [ self.left, self.right ]).optimize(compressor); } var rr = self.right.evaluate(compressor); if (!rr) { if (compressor.option("booleans") && compressor.in_boolean_context()) { compressor.warn("Boolean && always false [{file}:{line},{col}]", self.start); return make_sequence(self, [ self.left, make_node(AST_False, self) ]).optimize(compressor); } else self.falsy = true; } else if (!(rr instanceof AST_Node)) { var parent = compressor.parent(); if (parent.operator == "&&" && parent.left === compressor.self() || compressor.option("booleans") && compressor.in_boolean_context()) { compressor.warn("Dropping side-effect-free && [{file}:{line},{col}]", self.start); return self.left.optimize(compressor); } } // x || false && y ---> x ? y : false if (self.left.operator == "||") { var lr = self.left.right.evaluate(compressor); if (!lr) return make_node(AST_Conditional, self, { condition: self.left.left, consequent: self.right, alternative: self.left.right }).optimize(compressor); } break; case "||": var ll = fuzzy_eval(self.left); if (!ll) { compressor.warn("Condition left of || always false [{file}:{line},{col}]", self.start); return make_sequence(self, [ self.left, self.right ]).optimize(compressor); } else if (!(ll instanceof AST_Node)) { compressor.warn("Condition left of || always true [{file}:{line},{col}]", self.start); return maintain_this_binding(compressor.parent(), compressor.self(), self.left).optimize(compressor); } var rr = self.right.evaluate(compressor); if (!rr) { var parent = compressor.parent(); if (parent.operator == "||" && parent.left === compressor.self() || compressor.option("booleans") && compressor.in_boolean_context()) { compressor.warn("Dropping side-effect-free || [{file}:{line},{col}]", self.start); return self.left.optimize(compressor); } } else if (!(rr instanceof AST_Node)) { if (compressor.option("booleans") && compressor.in_boolean_context()) { compressor.warn("Boolean || always true [{file}:{line},{col}]", self.start); return make_sequence(self, [ self.left, make_node(AST_True, self) ]).optimize(compressor); } else self.truthy = true; } if (self.left.operator == "&&") { var lr = self.left.right.evaluate(compressor); if (lr && !(lr instanceof AST_Node)) return make_node(AST_Conditional, self, { condition: self.left.left, consequent: self.left.right, alternative: self.right }).optimize(compressor); } break; } var associative = true; switch (self.operator) { case "+": // "foo" + ("bar" + x) => "foobar" + x 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, self.left, { value: "" + self.left.getValue() + self.right.left.getValue(), start: self.left.start, end: self.right.left.end }), right: self.right.right }); } // (x + "foo") + "bar" => x + "foobar" 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, self.right, { value: "" + self.left.right.getValue() + self.right.getValue(), start: self.left.right.start, end: self.right.end }) }); } // (x + "foo") + ("bar" + y) => (x + "foobar") + y 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, self.left.right, { value: "" + self.left.right.getValue() + self.right.left.getValue(), start: self.left.right.start, end: self.right.left.end }) }), right: self.right.right }); } // a + -b => a - b if (self.right instanceof AST_UnaryPrefix && self.right.operator == "-" && self.left.is_number(compressor)) { self = make_node(AST_Binary, self, { operator: "-", left: self.left, right: self.right.expression }); break; } // -a + b => b - a if (self.left instanceof AST_UnaryPrefix && self.left.operator == "-" && reversible() && self.right.is_number(compressor)) { self = make_node(AST_Binary, self, { operator: "-", left: self.right, right: self.left.expression }); break; } case "*": associative = compressor.option("unsafe_math"); case "&": case "|": case "^": // a + +b => +b + a if (self.left.is_number(compressor) && self.right.is_number(compressor) && reversible() && !(self.left instanceof AST_Binary && self.left.operator != self.operator && PRECEDENCE[self.left.operator] >= PRECEDENCE[self.operator])) { var reversed = make_node(AST_Binary, self, { operator: self.operator, left: self.right, right: self.left }); if (self.right instanceof AST_Constant && !(self.left instanceof AST_Constant)) { self = best_of(compressor, reversed, self); } else { self = best_of(compressor, self, reversed); } } if (associative && self.is_number(compressor)) { // a + (b + c) => (a + b) + c if (self.right instanceof AST_Binary && self.right.operator == self.operator) { self = make_node(AST_Binary, self, { operator: self.operator, left: make_node(AST_Binary, self.left, { operator: self.operator, left: self.left, right: self.right.left, start: self.left.start, end: self.right.left.end }), right: self.right.right }); } // (n + 2) + 3 => 5 + n // (2 * n) * 3 => 6 + n if (self.right instanceof AST_Constant && self.left instanceof AST_Binary && self.left.operator == self.operator) { if (self.left.left instanceof AST_Constant) { self = make_node(AST_Binary, self, { operator: self.operator, left: make_node(AST_Binary, self.left, { operator: self.operator, left: self.left.left, right: self.right, start: self.left.left.start, end: self.right.end }), right: self.left.right }); } else if (self.left.right instanceof AST_Constant) { self = make_node(AST_Binary, self, { operator: self.operator, left: make_node(AST_Binary, self.left, { operator: self.operator, left: self.left.right, right: self.right, start: self.left.right.start, end: self.right.end }), right: self.left.left }); } } // (a | 1) | (2 | d) => (3 | a) | b if (self.left instanceof AST_Binary && self.left.operator == self.operator && self.left.right instanceof AST_Constant && self.right instanceof AST_Binary && self.right.operator == self.operator && self.right.left instanceof AST_Constant) { self = make_node(AST_Binary, self, { operator: self.operator, left: make_node(AST_Binary, self.left, { operator: self.operator, left: make_node(AST_Binary, self.left.left, { operator: self.operator, left: self.left.right, right: self.right.left, start: self.left.right.start, end: self.right.left.end }), right: self.left.left }), right: self.right.right }); } } } } // x && (y && z) ==> x && y && z // x || (y || z) ==> x || y || z // x + ("y" + z) ==> x + "y" + z // "x" + (y + "z")==> "x" + y + "z" if (self.right instanceof AST_Binary && self.right.operator == self.operator && (lazy_op[self.operator] || (self.operator == "+" && (self.right.left.is_string(compressor) || (self.left.is_string(compressor) && self.right.right.is_string(compressor)))))) { 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); } var ev = self.evaluate(compressor); if (ev !== self) { ev = make_node_from_constant(ev, self).optimize(compressor); return best_of(compressor, ev, self); } return self; function fuzzy_eval(node) { if (node.truthy) return true; if (node.falsy) return false; if (node.is_truthy()) return true; return node.evaluate(compressor); } }); function recursive_ref(compressor, def) { var node; for (var i = 0; node = compressor.parent(i); i++) { if (node instanceof AST_Lambda) { var name = node.name; if (name && name.definition() === def) break; } } return node; } OPT(AST_SymbolRef, function(self, compressor){ var def = self.resolve_defines(compressor); if (def) { return def.optimize(compressor); } // testing against !self.scope.uses_with first is an optimization if (!compressor.option("ie8") && is_undeclared_ref(self) && (!self.scope.uses_with || !compressor.find_parent(AST_With))) { switch (self.name) { case "undefined": return make_node(AST_Undefined, self).optimize(compressor); case "NaN": return make_node(AST_NaN, self).optimize(compressor); case "Infinity": return make_node(AST_Infinity, self).optimize(compressor); } } if (compressor.option("reduce_vars") && is_lhs(self, compressor.parent()) !== self) { var d = self.definition(); var fixed = self.fixed_value(); var single_use = d.single_use; if (single_use && fixed instanceof AST_Lambda) { if (d.scope !== self.scope && (!compressor.option("reduce_funcs") || d.escaped == 1 || fixed.inlined)) { single_use = false; } else if (recursive_ref(compressor, d)) { single_use = false; } else if (d.scope !== self.scope || d.orig[0] instanceof AST_SymbolFunarg) { single_use = fixed.is_constant_expression(self.scope); if (single_use == "f") { var scope = self.scope; do { if (scope instanceof AST_Defun || scope instanceof AST_Function) { scope.inlined = true; } } while (scope = scope.parent_scope); } } } if (single_use && fixed) { if (fixed instanceof AST_Defun) { fixed._squeezed = true; fixed = make_node(AST_Function, fixed, fixed); } var value; if (d.recursive_refs > 0 && fixed.name instanceof AST_SymbolDefun) { value = fixed.clone(true); var defun_def = value.name.definition(); var lambda_def = value.variables.get(value.name.name); var name = lambda_def && lambda_def.orig[0]; if (!(name instanceof AST_SymbolLambda)) { name = make_node(AST_SymbolLambda, value.name, value.name); name.scope = value; value.name = name; lambda_def = value.def_function(name); } value.walk(new TreeWalker(function(node) { if (node instanceof AST_SymbolRef && node.definition() === defun_def) { node.thedef = lambda_def; lambda_def.references.push(node); } })); } else { value = fixed.optimize(compressor); if (value === fixed) value = fixed.clone(true); } return value; } if (fixed && d.should_replace === undefined) { var init; if (fixed instanceof AST_This) { if (!(d.orig[0] instanceof AST_SymbolFunarg) && all(d.references, function(ref) { return d.scope === ref.scope; })) { init = fixed; } } else { var ev = fixed.evaluate(compressor); if (ev !== fixed && (compressor.option("unsafe_regexp") || !(ev instanceof RegExp))) { init = make_node_from_constant(ev, fixed); } } if (init) { var value_length = init.optimize(compressor).print_to_string().length; var fn; if (has_symbol_ref(fixed)) { fn = function() { var result = init.optimize(compressor); return result === init ? result.clone(true) : result; }; } else { value_length = Math.min(value_length, fixed.print_to_string().length); fn = function() { var result = best_of_expression(init.optimize(compressor), fixed); return result === init || result === fixed ? result.clone(true) : result; }; } var name_length = d.name.length; var overhead = 0; if (compressor.option("unused") && !compressor.exposed(d)) { overhead = (name_length + 2 + value_length) / (d.references.length - d.assignments); } d.should_replace = value_length <= name_length + overhead ? fn : false; } else { d.should_replace = false; } } if (d.should_replace) { return d.should_replace(); } } return self; function has_symbol_ref(value) { var found; value.walk(new TreeWalker(function(node) { if (node instanceof AST_SymbolRef) found = true; if (found) return true; })); return found; } }); function is_atomic(lhs, self) { return lhs instanceof AST_SymbolRef || lhs.TYPE === self.TYPE; } OPT(AST_Undefined, function(self, compressor){ if (compressor.option("unsafe_undefined")) { var undef = find_variable(compressor, "undefined"); if (undef) { var ref = make_node(AST_SymbolRef, self, { name : "undefined", scope : undef.scope, thedef : undef }); ref.is_undefined = true; return ref; } } var lhs = is_lhs(compressor.self(), compressor.parent()); if (lhs && is_atomic(lhs, self)) return self; return make_node(AST_UnaryPrefix, self, { operator: "void", expression: make_node(AST_Number, self, { value: 0 }) }); }); OPT(AST_Infinity, function(self, compressor){ var lhs = is_lhs(compressor.self(), compressor.parent()); if (lhs && is_atomic(lhs, self)) return self; if (compressor.option("keep_infinity") && !(lhs && !is_atomic(lhs, self)) && !find_variable(compressor, "Infinity")) return self; return make_node(AST_Binary, self, { operator: "/", left: make_node(AST_Number, self, { value: 1 }), right: make_node(AST_Number, self, { value: 0 }) }); }); OPT(AST_NaN, function(self, compressor){ var lhs = is_lhs(compressor.self(), compressor.parent()); if (lhs && !is_atomic(lhs, self) || find_variable(compressor, "NaN")) { return make_node(AST_Binary, self, { operator: "/", left: make_node(AST_Number, self, { value: 0 }), right: make_node(AST_Number, self, { value: 0 }) }); } return self; }); function is_reachable(self, defs) { var reachable = false; var find_ref = new TreeWalker(function(node) { if (reachable) return true; if (node instanceof AST_SymbolRef && member(node.definition(), defs)) { return reachable = true; } }); var scan_scope = new TreeWalker(function(node) { if (reachable) return true; if (node instanceof AST_Scope && node !== self) { var parent = scan_scope.parent(); if (parent instanceof AST_Call && parent.expression === node) return; node.walk(find_ref); return true; } }); self.walk(scan_scope); return reachable; } var ASSIGN_OPS = makePredicate("+ - / * % >> << >>> | ^ &"); var ASSIGN_OPS_COMMUTATIVE = makePredicate("* | ^ &"); OPT(AST_Assign, function(self, compressor){ var def; if (compressor.option("dead_code") && self.left instanceof AST_SymbolRef && (def = self.left.definition()).scope === compressor.find_parent(AST_Lambda)) { var level = 0, node, parent = self; do { node = parent; parent = compressor.parent(level++); if (parent instanceof AST_Exit) { if (in_try(level, parent)) break; if (is_reachable(def.scope, [ def ])) break; if (self.operator == "=") return self.right; def.fixed = false; return make_node(AST_Binary, self, { operator: self.operator.slice(0, -1), left: self.left, right: self.right }).optimize(compressor); } } while (parent instanceof AST_Binary && parent.right === node || parent instanceof AST_Sequence && parent.tail_node() === node); } self = self.lift_sequences(compressor); if (self.operator == "=" && self.left instanceof AST_SymbolRef && self.right instanceof AST_Binary) { // x = expr1 OP expr2 if (self.right.left instanceof AST_SymbolRef && self.right.left.name == self.left.name && ASSIGN_OPS[self.right.operator]) { // x = x - 2 ---> x -= 2 self.operator = self.right.operator + "="; self.right = self.right.right; } else if (self.right.right instanceof AST_SymbolRef && self.right.right.name == self.left.name && ASSIGN_OPS_COMMUTATIVE[self.right.operator] && !self.right.left.has_side_effects(compressor)) { // x = 2 & x ---> x &= 2 self.operator = self.right.operator + "="; self.right = self.right.left; } } return self; function in_try(level, node) { var right = self.right; self.right = make_node(AST_Null, right); var may_throw = node.may_throw(compressor); self.right = right; var scope = self.left.definition().scope; var parent; while ((parent = compressor.parent(level++)) !== scope) { if (parent instanceof AST_Try) { if (parent.bfinally) return true; if (may_throw && parent.bcatch) return true; } } } }); OPT(AST_Conditional, function(self, compressor){ if (!compressor.option("conditionals")) return self; // This looks like lift_sequences(), should probably be under "sequences" if (self.condition instanceof AST_Sequence) { var expressions = self.condition.expressions.slice(); self.condition = expressions.pop(); expressions.push(self); return make_sequence(self, expressions); } var cond = self.condition.is_truthy() || self.condition.tail_node().evaluate(compressor); if (!cond) { compressor.warn("Condition always false [{file}:{line},{col}]", self.start); return make_sequence(self, [ self.condition, self.alternative ]).optimize(compressor); } else if (!(cond instanceof AST_Node)) { compressor.warn("Condition always true [{file}:{line},{col}]", self.start); return make_sequence(self, [ self.condition, self.consequent ]).optimize(compressor); } var negated = cond.negate(compressor, first_in_statement(compressor)); if (best_of(compressor, cond, negated) === negated) { self = make_node(AST_Conditional, self, { condition: negated, consequent: self.alternative, alternative: self.consequent }); } var condition = self.condition; var consequent = self.consequent; var alternative = self.alternative; // x?x:y --> x||y if (condition instanceof AST_SymbolRef && consequent instanceof AST_SymbolRef && condition.definition() === consequent.definition()) { return make_node(AST_Binary, self, { operator: "||", left: condition, right: alternative }); } // if (foo) exp = something; else exp = something_else; // | // v // exp = foo ? something : something_else; if (consequent instanceof AST_Assign && alternative instanceof AST_Assign && consequent.operator == alternative.operator && consequent.left.equivalent_to(alternative.left) && (!self.condition.has_side_effects(compressor) || consequent.operator == "=" && !consequent.left.has_side_effects(compressor))) { 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 }) }); } // x ? y(a) : y(b) --> y(x ? a : b) var arg_index; if (consequent instanceof AST_Call && alternative.TYPE === consequent.TYPE && consequent.args.length > 0 && consequent.args.length == alternative.args.length && consequent.expression.equivalent_to(alternative.expression) && !self.condition.has_side_effects(compressor) && !consequent.expression.has_side_effects(compressor) && typeof (arg_index = single_arg_diff()) == "number") { var node = consequent.clone(); node.args[arg_index] = make_node(AST_Conditional, self, { condition: self.condition, consequent: consequent.args[arg_index], alternative: alternative.args[arg_index] }); return node; } // 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 : y --> x, y if (consequent.equivalent_to(alternative)) { return make_sequence(self, [ self.condition, consequent ]).optimize(compressor); } // x ? (y, w) : (z, w) --> x ? y : z, w if ((consequent instanceof AST_Sequence || alternative instanceof AST_Sequence) && consequent.tail_node().equivalent_to(alternative.tail_node())) { return make_sequence(self, [ make_node(AST_Conditional, self, { condition: self.condition, consequent: pop_seq(consequent), alternative: pop_seq(alternative) }), consequent.tail_node() ]).optimize(compressor); } // x ? y || z : z --> x && y || z if (consequent instanceof AST_Binary && consequent.operator == "||" && consequent.right.equivalent_to(alternative)) { return make_node(AST_Binary, self, { operator: "||", left: make_node(AST_Binary, self, { operator: "&&", left: self.condition, right: consequent.left }), right: alternative }).optimize(compressor); } var in_bool = compressor.option("booleans") && compressor.in_boolean_context(); if (is_true(self.consequent)) { if (is_false(self.alternative)) { // c ? true : false ---> !!c return booleanize(self.condition); } // c ? true : x ---> !!c || x return make_node(AST_Binary, self, { operator: "||", left: booleanize(self.condition), right: self.alternative }); } if (is_false(self.consequent)) { if (is_true(self.alternative)) { // c ? false : true ---> !c return booleanize(self.condition.negate(compressor)); } // c ? false : x ---> !c && x return make_node(AST_Binary, self, { operator: "&&", left: booleanize(self.condition.negate(compressor)), right: self.alternative }); } if (is_true(self.alternative)) { // c ? x : true ---> !c || x return make_node(AST_Binary, self, { operator: "||", left: booleanize(self.condition.negate(compressor)), right: self.consequent }); } if (is_false(self.alternative)) { // c ? x : false ---> !!c && x return make_node(AST_Binary, self, { operator: "&&", left: booleanize(self.condition), right: self.consequent }); } return self; function booleanize(node) { if (node.is_boolean()) return node; // !!expression return make_node(AST_UnaryPrefix, node, { operator: "!", expression: node.negate(compressor) }); } // AST_True or !0 function is_true(node) { return node instanceof AST_True || in_bool && node instanceof AST_Constant && node.getValue() || (node instanceof AST_UnaryPrefix && node.operator == "!" && node.expression instanceof AST_Constant && !node.expression.getValue()); } // AST_False or !1 function is_false(node) { return node instanceof AST_False || in_bool && node instanceof AST_Constant && !node.getValue() || (node instanceof AST_UnaryPrefix && node.operator == "!" && node.expression instanceof AST_Constant && node.expression.getValue()); } function single_arg_diff() { var a = consequent.args; var b = alternative.args; for (var i = 0, len = a.length; i < len; i++) { if (!a[i].equivalent_to(b[i])) { for (var j = i + 1; j < len; j++) { if (!a[j].equivalent_to(b[j])) return; } return i; } } } function pop_seq(node) { if (!(node instanceof AST_Sequence)) return make_node(AST_Number, node, { value: 0 }); return make_sequence(node, node.expressions.slice(0, -1)); } }); OPT(AST_Boolean, function(self, compressor){ if (!compressor.option("booleans")) return self; if (compressor.in_boolean_context()) return make_node(AST_Number, self, { value: +self.value }); 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 }) }); }); OPT(AST_Sub, function(self, compressor){ var expr = self.expression; var prop = self.property; if (compressor.option("properties")) { var key = prop.evaluate(compressor); if (key !== prop) { if (typeof key == "string") { if (key == "undefined") { key = undefined; } else { var value = parseFloat(key); if (value.toString() == key) { key = value; } } } prop = self.property = best_of_expression(prop, make_node_from_constant(key, prop).transform(compressor)); var property = "" + key; if (is_identifier_string(property) && property.length <= prop.print_to_string().length + 1) { return make_node(AST_Dot, self, { expression: expr, property: property }).optimize(compressor); } } } if (is_lhs(self, compressor.parent())) return self; if (key !== prop) { var sub = self.flatten_object(property, compressor); if (sub) { expr = self.expression = sub.expression; prop = self.property = sub.property; } } if (compressor.option("properties") && compressor.option("side_effects") && prop instanceof AST_Number && expr instanceof AST_Array) { var index = prop.getValue(); var elements = expr.elements; if (index in elements) { var flatten = true; var values = []; for (var i = elements.length; --i > index;) { var value = elements[i].drop_side_effect_free(compressor); if (value) { values.unshift(value); if (flatten && value.has_side_effects(compressor)) flatten = false; } } var retValue = elements[index]; retValue = retValue instanceof AST_Hole ? make_node(AST_Undefined, retValue) : retValue; if (!flatten) values.unshift(retValue); while (--i >= 0) { var value = elements[i].drop_side_effect_free(compressor); if (value) values.unshift(value); else index--; } if (flatten) { values.push(retValue); return make_sequence(self, values).optimize(compressor); } else return make_node(AST_Sub, self, { expression: make_node(AST_Array, expr, { elements: values }), property: make_node(AST_Number, prop, { value: index }) }); } } var fn; if (compressor.option("arguments") && expr instanceof AST_SymbolRef && expr.name == "arguments" && expr.definition().orig.length == 1 && (fn = expr.scope) instanceof AST_Lambda && prop instanceof AST_Number) { var index = prop.getValue(); var argname = fn.argnames[index]; if (!argname && !compressor.option("keep_fargs")) { while (index >= fn.argnames.length) { argname = make_node(AST_SymbolFunarg, fn, { name: fn.make_var_name("argument_" + fn.argnames.length), scope: fn }); fn.argnames.push(argname); fn.enclosed.push(fn.def_variable(argname)); } } if (argname) { var sym = make_node(AST_SymbolRef, self, argname); sym.reference({}); return sym; } } var ev = self.evaluate(compressor); if (ev !== self) { ev = make_node_from_constant(ev, self).optimize(compressor); return best_of(compressor, ev, self); } return self; }); AST_Lambda.DEFMETHOD("contains_this", function() { var result; var self = this; self.walk(new TreeWalker(function(node) { if (result) return true; if (node instanceof AST_This) return result = true; if (node !== self && node instanceof AST_Scope) return true; })); return result; }); AST_PropAccess.DEFMETHOD("flatten_object", function(key, compressor) { if (!compressor.option("properties")) return; var expr = this.expression; if (expr instanceof AST_Object) { var props = expr.properties; for (var i = props.length; --i >= 0;) { var prop = props[i]; if ("" + prop.key == key) { if (!all(props, function(prop) { return prop instanceof AST_ObjectKeyVal; })) break; var value = prop.value; if (value instanceof AST_Function && !(compressor.parent() instanceof AST_New) && value.contains_this()) break; return make_node(AST_Sub, this, { expression: make_node(AST_Array, expr, { elements: props.map(function(prop) { return prop.value; }) }), property: make_node(AST_Number, this, { value: i }) }); } } } }); OPT(AST_Dot, function(self, compressor){ if (self.property == "arguments" || self.property == "caller") { compressor.warn("Function.protoype.{prop} not supported [{file}:{line},{col}]", { prop: self.property, file: self.start.file, line: self.start.line, col: self.start.col }); } var def = self.resolve_defines(compressor); if (def) { return def.optimize(compressor); } if (is_lhs(self, compressor.parent())) return self; if (compressor.option("unsafe_proto") && self.expression instanceof AST_Dot && self.expression.property == "prototype") { var exp = self.expression.expression; if (is_undeclared_ref(exp)) switch (exp.name) { case "Array": self.expression = make_node(AST_Array, self.expression, { elements: [] }); break; case "Function": self.expression = make_node(AST_Function, self.expression, { argnames: [], body: [] }); break; case "Number": self.expression = make_node(AST_Number, self.expression, { value: 0 }); break; case "Object": self.expression = make_node(AST_Object, self.expression, { properties: [] }); break; case "RegExp": self.expression = make_node(AST_RegExp, self.expression, { value: /t/ }); break; case "String": self.expression = make_node(AST_String, self.expression, { value: "" }); break; } } var sub = self.flatten_object(self.property, compressor); if (sub) return sub.optimize(compressor); var ev = self.evaluate(compressor); if (ev !== self) { ev = make_node_from_constant(ev, self).optimize(compressor); return best_of(compressor, ev, self); } return self; }); OPT(AST_Return, function(self, compressor){ if (self.value && is_undefined(self.value, compressor)) { self.value = null; } return self; }); OPT(AST_VarDef, function(self, compressor){ var defines = compressor.option("global_defs"); if (defines && HOP(defines, self.name.name)) { compressor.warn('global_defs ' + self.name.name + ' redefined [{file}:{line},{col}]', self.start); } return self; }); })(); tonglan/adminSystem - Gogs: Go Git Service

1 次代碼提交 (11e3a9652a62b867d722e5aebdba895bb86f9ed3)

作者 SHA1 備註 提交日期
  FFIB 11e3a9652a first 8 年之前
Kodo/kodo - Gogs: Go Git Service

3 次代碼提交 (56b82eb2f9d82cbbdde18cefa3f6858e83224446)

作者 SHA1 備註 提交日期
  Brightcells 018d52f61d Update package django_xxx 8 年之前
  Brightcells 208fc20a24 Change to use django-response 8 年之前
  Brightcells 4547c709bf Add api get_server_time_api 9 年之前