/*!
- * HTMLMinifier v3.4.0 (http://kangax.github.io/html-minifier/)
+ * HTMLMinifier v3.4.1 (http://kangax.github.io/html-minifier/)
* Copyright 2010-2017 Juriy "kangax" Zaytsev
* Licensed under the MIT license
*/
function string_template(text, props) {
return text.replace(/\{(.+?)\}/g, function(str, p){
- return props[p];
+ return props && props[p];
});
};
}, null);
var AST_Node = DEFNODE("Node", "start end", {
- clone: function() {
+ _clone: function(deep) {
+ if (deep) {
+ var self = this.clone();
+ return self.transform(new TreeTransformer(function(node) {
+ if (node !== self) {
+ return node.clone(true);
+ }
+ }));
+ }
return new this.CTOR(this);
},
+ clone: function(deep) {
+ return this._clone(deep);
+ },
$documentation: "Base class of all AST nodes",
$propdoc: {
start: "[AST_Token] The first token of this node",
this.label._walk(visitor);
this.body._walk(visitor);
});
+ },
+ clone: function(deep) {
+ var node = this._clone(deep);
+ if (deep) {
+ var refs = node.label.references;
+ var label = this.label;
+ node.walk(new TreeWalker(function(node) {
+ if (node instanceof AST_LoopControl
+ && node.label && node.label.thedef === label) {
+ refs.push(node);
+ }
+ }));
+ }
+ return node;
}
}, AST_StatementWithBody);
var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", "init", {
$documentation: "A declaration symbol (symbol in var/const, function name or argument, symbol in catch)",
- $propdoc: {
- init: "[AST_Node*/S] array of initializers for this declaration."
- }
}, AST_Symbol);
var AST_SymbolVar = DEFNODE("SymbolVar", null, {
function next_token(force_regexp) {
if (force_regexp != null)
return read_regexp(force_regexp);
+ if (shebang && S.pos == 0 && looking_at("#!")) {
+ start_token();
+ forward(2);
+ skip_line_comment("comment5");
+ }
for (;;) {
skip_whitespace();
start_token();
if (PUNC_CHARS(ch)) return token("punc", next());
if (OPERATOR_CHARS(ch)) return read_operator();
if (code == 92 || is_identifier_start(code)) return read_word();
- if (shebang) {
- if (S.pos == 0 && looking_at("#!")) {
- forward(2);
- skip_line_comment("comment5");
- continue;
- }
- }
break;
}
parse_error("Unexpected character '" + ch + "'");
expression : parenthesised(),
body : statement()
});
-
- default:
- unexpected();
}
}
+ unexpected();
});
function labeled_statement() {
var labels = new Dictionary();
var defun = null;
var tw = new TreeWalker(function(node, descend){
- if (options.screw_ie8 && node instanceof AST_Catch) {
+ if (node instanceof AST_Catch) {
var save_scope = scope;
scope = new AST_Scope(node);
scope.init_scope_vars();
}
else if (node instanceof AST_SymbolVar
|| node instanceof AST_SymbolConst) {
- var def = defun.def_variable(node);
- def.init = tw.parent().value;
+ defun.def_variable(node);
}
else if (node instanceof AST_SymbolCatch) {
- (options.screw_ie8 ? scope : defun)
- .def_variable(node);
+ scope.def_variable(node);
}
else if (node instanceof AST_LabelRef) {
var sym = labels.get(node.name);
});
self.walk(tw);
+ // pass 3: fix up any scoping issue with IE8
+ if (!options.screw_ie8) {
+ self.walk(new TreeWalker(function(node, descend) {
+ if (node instanceof AST_SymbolCatch) {
+ var name = node.name;
+ var refs = node.thedef.references;
+ var scope = node.thedef.scope.parent_scope;
+ var def = scope.find_variable(name) || self.globals.get(name) || scope.def_variable(node);
+ refs.forEach(function(ref) {
+ ref.thedef = def;
+ ref.reference(options);
+ });
+ node.thedef = def;
+ return true;
+ }
+ }));
+ }
+
if (options.cache) {
this.cname = options.cache.cname;
}
var EXPECT_DIRECTIVE = /^$|[;{][\s\n]*$/;
function is_some_comments(comment) {
- var text = comment.value;
- var type = comment.type;
- if (type == "comment2") {
- // multiline comment
- return /@preserve|@license|@cc_on/i.test(text);
- }
- return type == "comment5";
-}
-
-function is_comment5(comment) {
- return comment.type == "comment5";
+ // multiline comment
+ return comment.type == "comment2" && /@preserve|@license|@cc_on/i.test(comment.value);
}
function OutputStream(options) {
}, true);
// Convert comment option to RegExp if neccessary and set up comments filter
- var comment_filter = options.shebang ? is_comment5 : return_false; // Default case, throw all comments away except shebangs
+ var comment_filter = return_false; // Default case, throw all comments away
if (options.comments) {
var comments = options.comments;
if (typeof options.comments === "string" && /^\/.*\/[a-zA-Z]*$/.test(options.comments)) {
}
if (comments instanceof RegExp) {
comment_filter = function(comment) {
- return comment.type == "comment5" || comments.test(comment.value);
+ return comment.type != "comment5" && comments.test(comment.value);
};
}
else if (typeof comments === "function") {
comment_filter = function(comment) {
- return comment.type == "comment5" || comments(this, comment);
+ return comment.type != "comment5" && comments(this, comment);
};
}
else if (comments === "some") {
return OUTPUT;
};
- if (options.preamble) {
- print(options.preamble.replace(/\r\n?|[\n\u2028\u2029]|\s*$/g, "\n"));
- }
-
var stack = [];
return {
get : get,
}));
}
+ if (comments.length > 0 && output.pos() == 0) {
+ if (output.option("shebang") && comments[0].type == "comment5") {
+ output.print("#!" + comments.shift().value + "\n");
+ output.indent();
+ }
+ var preamble = output.option("preamble");
+ if (preamble) {
+ output.print(preamble.replace(/\r\n?|[\n\u2028\u2029]|\s*$/g, "\n"));
+ }
+ }
+
comments = comments.filter(output.comment_filter, self);
// Keep single line comments after nlb, after nlb
output.space();
}
}
- else if (output.pos() === 0 && c.type == "comment5" && output.option("shebang")) {
- output.print("#!" + c.value + "\n");
- output.indent();
- }
});
}
});
DEFPRINT(AST_Do, function(self, output){
output.print("do");
output.space();
- self._do_print_body(output);
+ make_block(self.body, output);
output.space();
output.print("while");
output.space();
/* -----[ if ]----- */
function make_then(self, output) {
- if (output.option("bracketize")) {
- make_block(self.body, output);
- return;
- }
+ var b = self.body;
+ if (output.option("bracketize")
+ || !output.option("screw_ie8") && b instanceof AST_Do)
+ return make_block(b, output);
// The squeezer replaces "block"-s that contain only a single
// statement with the statement itself; technically, the AST
// is correct, but this can create problems when we output an
// IF *without* an ELSE block (then the outer ELSE would refer
// to the inner IF). This function checks for this case and
// adds the block brackets if needed.
- if (!self.body)
- return output.force_semicolon();
- if (self.body instanceof AST_Do) {
- // Unconditionally use the if/do-while workaround for all browsers.
- // https://github.com/mishoo/UglifyJS/issues/#issue/57 IE
- // croaks with "syntax error" on code like this: if (foo)
- // do ... while(cond); else ... we need block brackets
- // around do/while
- make_block(self.body, output);
- return;
- }
- var b = self.body;
+ if (!b) return output.force_semicolon();
while (true) {
if (b instanceof AST_If) {
if (!b.alternative) {
function force_statement(stat, output) {
if (output.option("bracketize")) {
- if (!stat || stat instanceof AST_EmptyStatement)
- output.print("{}");
- else if (stat instanceof AST_BlockStatement)
- stat.print(output);
- else output.with_block(function(){
- output.indent();
- stat.print(output);
- output.newline();
- });
+ make_block(stat, output);
} else {
if (!stat || stat instanceof AST_EmptyStatement)
output.force_semicolon();
};
function make_block(stmt, output) {
- if (stmt instanceof AST_BlockStatement) {
+ if (!stmt || stmt instanceof AST_EmptyStatement)
+ output.print("{}");
+ else if (stmt instanceof AST_BlockStatement)
stmt.print(output);
- return;
- }
- output.with_block(function(){
+ else output.with_block(function(){
output.indent();
stmt.print(output);
output.newline();
drop_debugger : !false_by_default,
unsafe : false,
unsafe_comps : false,
+ unsafe_math : false,
unsafe_proto : false,
conditionals : !false_by_default,
comparisons : !false_by_default,
booleans : !false_by_default,
loops : !false_by_default,
unused : !false_by_default,
- toplevel : !!options["top_retain"],
+ toplevel : !!(options && options["top_retain"]),
top_retain : null,
hoist_funs : !false_by_default,
keep_fargs : true,
screw_ie8 : true,
drop_console : false,
angular : false,
+ expression : false,
warnings : true,
global_defs : {},
passes : 1,
merge(Compressor.prototype, {
option: function(key) { return this.options[key] },
compress: function(node) {
+ if (this.option("expression")) {
+ node = node.process_expression(true);
+ }
var passes = +this.options.passes || 1;
for (var pass = 0; pass < passes && pass < 3; ++pass) {
if (pass > 0 || this.option("reduce_vars"))
node.reset_opt_flags(this, true);
node = node.transform(this);
}
+ if (this.option("expression")) {
+ node = node.process_expression(false);
+ }
return node;
},
warn: function(text, props) {
return this.print_to_string() == node.print_to_string();
});
+ AST_Node.DEFMETHOD("process_expression", function(insert) {
+ var self = this;
+ var tt = new TreeTransformer(function(node) {
+ if (insert && node instanceof AST_SimpleStatement) {
+ return make_node(AST_Return, node, {
+ value: node.body
+ });
+ }
+ if (!insert && node instanceof AST_Return) {
+ return make_node(AST_SimpleStatement, node, {
+ body: node.value || make_node(AST_Undefined, node)
+ });
+ }
+ if (node instanceof AST_Lambda && node !== self) {
+ return node;
+ }
+ if (node instanceof AST_Block) {
+ var index = node.body.length - 1;
+ if (index >= 0) {
+ node.body[index] = node.body[index].transform(tt);
+ }
+ }
+ if (node instanceof AST_If) {
+ node.body = node.body.transform(tt);
+ if (node.alternative) {
+ node.alternative = node.alternative.transform(tt);
+ }
+ }
+ if (node instanceof AST_With) {
+ node.body = node.body.transform(tt);
+ }
+ return node;
+ });
+ return self.transform(tt);
+ });
+
AST_Node.DEFMETHOD("reset_opt_flags", function(compressor, rescan){
var reduce_vars = rescan && compressor.option("reduce_vars");
- var unsafe = compressor.option("unsafe");
- var tw = new TreeWalker(function(node){
+ var toplevel = compressor.option("toplevel");
+ var ie8 = !compressor.option("screw_ie8");
+ var safe_ids = [];
+ push();
+ var suppressor = new TreeWalker(function(node) {
+ if (node instanceof AST_Symbol) {
+ var d = node.definition();
+ if (node instanceof AST_SymbolRef) d.references.push(node);
+ d.fixed = false;
+ }
+ });
+ var tw = new TreeWalker(function(node, descend){
+ if (!(node instanceof AST_Directive || node instanceof AST_Constant)) {
+ node._squeezed = false;
+ node._optimized = false;
+ }
if (reduce_vars) {
if (node instanceof AST_Toplevel) node.globals.each(reset_def);
if (node instanceof AST_Scope) node.variables.each(reset_def);
if (node instanceof AST_SymbolRef) {
var d = node.definition();
d.references.push(node);
- if (!d.modified && (d.orig.length > 1 || isModified(node, 0))) {
- d.modified = true;
+ if (!d.fixed || !is_safe(d)
+ || is_modified(node, 0, d.fixed instanceof AST_Lambda)) {
+ d.fixed = false;
}
}
- if (node instanceof AST_Call && node.expression instanceof AST_Function) {
- node.expression.argnames.forEach(function(arg, i) {
- arg.definition().init = node.args[i] || make_node(AST_Undefined, node);
+ if (ie8 && node instanceof AST_SymbolCatch) {
+ node.definition().fixed = false;
+ }
+ if (node instanceof AST_VarDef) {
+ var d = node.name.definition();
+ if (d.fixed === undefined) {
+ d.fixed = node.value || make_node(AST_Undefined, node);
+ mark_as_safe(d);
+ } else {
+ d.fixed = false;
+ }
+ }
+ if (node instanceof AST_Defun) {
+ var d = node.name.definition();
+ if (!toplevel && d.global || is_safe(d)) {
+ d.fixed = false;
+ } else {
+ d.fixed = node;
+ mark_as_safe(d);
+ }
+ var save_ids = safe_ids;
+ safe_ids = [];
+ push();
+ descend();
+ safe_ids = save_ids;
+ return true;
+ }
+ var iife;
+ if (node instanceof AST_Function
+ && !node.name
+ && (iife = tw.parent()) instanceof AST_Call
+ && iife.expression === node) {
+ // Virtually turn IIFE parameters into variable definitions:
+ // (function(a,b) {...})(c,d) => (function() {var a=c,b=d; ...})()
+ // So existing transformation rules can work on them.
+ node.argnames.forEach(function(arg, i) {
+ var d = arg.definition();
+ d.fixed = iife.args[i] || make_node(AST_Undefined, iife);
+ mark_as_safe(d);
});
}
- }
- if (!(node instanceof AST_Directive || node instanceof AST_Constant)) {
- node._squeezed = false;
- node._optimized = false;
+ if (node instanceof AST_If || node instanceof AST_DWLoop) {
+ node.condition.walk(tw);
+ push();
+ node.body.walk(tw);
+ pop();
+ if (node.alternative) {
+ push();
+ node.alternative.walk(tw);
+ pop();
+ }
+ return true;
+ }
+ if (node instanceof AST_LabeledStatement) {
+ push();
+ node.body.walk(tw);
+ pop();
+ return true;
+ }
+ if (node instanceof AST_For) {
+ if (node.init) node.init.walk(tw);
+ push();
+ if (node.condition) node.condition.walk(tw);
+ node.body.walk(tw);
+ if (node.step) node.step.walk(tw);
+ pop();
+ return true;
+ }
+ if (node instanceof AST_ForIn) {
+ node.init.walk(suppressor);
+ node.object.walk(tw);
+ push();
+ node.body.walk(tw);
+ pop();
+ return true;
+ }
+ if (node instanceof AST_Catch) {
+ push();
+ descend();
+ pop();
+ return true;
+ }
}
});
this.walk(tw);
+ function mark_as_safe(def) {
+ safe_ids[safe_ids.length - 1][def.id] = true;
+ }
+
+ function is_safe(def) {
+ for (var i = safe_ids.length, id = def.id; --i >= 0;) {
+ if (safe_ids[i][id]) return true;
+ }
+ }
+
+ function push() {
+ safe_ids.push(Object.create(null));
+ }
+
+ function pop() {
+ safe_ids.pop();
+ }
+
function reset_def(def) {
- def.modified = false;
+ if (toplevel || !def.global || def.orig[0] instanceof AST_SymbolConst) {
+ def.fixed = undefined;
+ } else {
+ def.fixed = false;
+ }
def.references = [];
def.should_replace = undefined;
- if (unsafe && def.init) {
- def.init._evaluated = undefined;
- }
}
- function isModified(node, level) {
+ function is_modified(node, level, func) {
var parent = tw.parent(level);
if (isLHS(node, parent)
- || parent instanceof AST_Call && parent.expression === node) {
+ || !func && parent instanceof AST_Call && parent.expression === node) {
return true;
} else if (parent instanceof AST_PropAccess && parent.expression === node) {
- return isModified(parent, level + 1);
+ return !func && is_modified(parent, level + 1);
}
}
});
return make_node(AST_Number, orig, { value: val });
case "boolean":
- return make_node(val ? AST_True : AST_False, orig).transform(compressor);
+ return make_node(val ? AST_True : AST_False, orig).optimize(compressor);
case "undefined":
return make_node(AST_Undefined, orig).transform(compressor);
default:
// Constant single use vars can be replaced in any scope.
if (var_decl.value.is_constant()) {
var ctt = new TreeTransformer(function(node) {
+ var parent = ctt.parent();
+ if (parent instanceof AST_IterationStatement
+ && (parent.condition === node || parent.init === node)) {
+ return node;
+ }
if (node === ref)
- return replace_var(node, ctt.parent(), true);
+ return replace_var(node, parent, true);
});
stat.transform(ctt);
continue;
return statements;
function is_lvalue(node, parent) {
- return node instanceof AST_SymbolRef && (
- (parent instanceof AST_Assign && node === parent.left)
- || (parent instanceof AST_Unary && parent.expression === node
- && (parent.operator == "++" || parent.operator == "--")));
+ return node instanceof AST_SymbolRef && isLHS(node, parent);
}
function replace_var(node, parent, is_constant) {
if (is_lvalue(node, parent)) return node;
// Further optimize statement after substitution.
stat.reset_opt_flags(compressor);
- compressor.warn("Replacing " + (is_constant ? "constant" : "variable") +
+ compressor.warn("Collapsing " + (is_constant ? "constant" : "variable") +
" " + var_name + " [{file}:{line},{col}]", node.start);
CHANGED = true;
return value;
CHANGED = true;
stat = stat.clone();
stat.alternative = ret[0] || make_node(AST_Return, stat, {
- value: make_node(AST_Undefined, stat)
+ value: null
});
ret[0] = stat.transform(compressor);
continue loop;
&& !stat.alternative) {
CHANGED = true;
ret.push(make_node(AST_Return, ret[0], {
- value: make_node(AST_Undefined, ret[0])
+ value: null
}).transform(compressor));
ret.unshift(stat);
continue loop;
}));
};
+ function is_undefined(node) {
+ return node instanceof AST_Undefined || node.is_undefined;
+ }
+
/* -----[ boolean/negation helpers ]----- */
// methods to determine whether an expression has a boolean result type
node.DEFMETHOD("is_boolean", func);
});
+ // methods to determine if an expression has a numeric result type
+ (function (def){
+ def(AST_Node, return_false);
+ def(AST_Number, return_true);
+ var unary = makePredicate("+ - ~ ++ --");
+ def(AST_Unary, function(){
+ return unary(this.operator);
+ });
+ var binary = makePredicate("- * / % & | ^ << >> >>>");
+ def(AST_Binary, function(compressor){
+ return binary(this.operator) || this.operator == "+"
+ && this.left.is_number(compressor)
+ && this.right.is_number(compressor);
+ });
+ var assign = makePredicate("-= *= /= %= &= |= ^= <<= >>= >>>=");
+ def(AST_Assign, function(compressor){
+ return assign(this.operator) || this.right.is_number(compressor);
+ });
+ def(AST_Seq, function(compressor){
+ return this.cdr.is_number(compressor);
+ });
+ def(AST_Conditional, function(compressor){
+ return this.consequent.is_number(compressor) && this.alternative.is_number(compressor);
+ });
+ })(function(node, func){
+ node.DEFMETHOD("is_number", func);
+ });
+
// methods to determine if an expression has a string result type
(function (def){
def(AST_Node, return_false);
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 isLHS(node, parent) {
- return parent instanceof AST_Unary && (parent.operator === "++" || parent.operator === "--")
+ return parent instanceof AST_Unary && (parent.operator == "++" || parent.operator == "--")
|| parent instanceof AST_Assign && parent.left === node;
}
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.
+ def(AST_Lambda, function(){
throw def;
});
function ev(node, compressor) {
for (var i = 0, len = this.properties.length; i < len; i++) {
var prop = this.properties[i];
var key = prop.key;
- if (key instanceof AST_Node) {
+ if (key instanceof AST_Symbol) {
+ key = key.name;
+ } else if (key instanceof AST_Node) {
key = ev(key, compressor);
}
if (typeof Object.prototype[key] === 'function') {
this._evaluating = true;
try {
var d = this.definition();
- if (compressor.option("reduce_vars") && !d.modified && d.init) {
+ if (compressor.option("reduce_vars") && d.fixed) {
if (compressor.option("unsafe")) {
- if (d.init._evaluated === undefined) {
- d.init._evaluated = ev(d.init, compressor);
+ if (!HOP(d.fixed, "_evaluated")) {
+ d.fixed._evaluated = ev(d.fixed, compressor);
}
- return d.init._evaluated;
+ return d.fixed._evaluated;
}
- return ev(d.init, compressor);
+ return ev(d.fixed, compressor);
}
} finally {
this._evaluating = false;
&& (comments = this.start.comments_before)
&& comments.length
&& /[@#]__PURE__/.test((last_comment = comments[comments.length - 1]).value)) {
- compressor.warn("Dropping __PURE__ call [{file}:{line},{col}]", this.start);
- last_comment.value = last_comment.value.replace(/[@#]__PURE__/g, ' ');
- pure = true;
+ pure = last_comment;
}
return this.pure = pure;
});
node.name = null;
}
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.definition().id in in_use_ids)) {
+ var trim = !compressor.option("keep_fargs");
+ for (var a = node.argnames, i = a.length; --i >= 0;) {
+ var sym = a[i];
+ if (!(sym.definition().id in in_use_ids)) {
+ sym.__unused = true;
+ if (trim) {
a.pop();
compressor.warn("Dropping unused function argument {name} [{file}:{line},{col}]", {
name : sym.name,
col : sym.start.col
});
}
- else break;
+ }
+ else {
+ trim = false;
}
}
}
}
if (drop_vars && node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn)) {
var def = node.definitions.filter(function(def){
+ if (def.value) def.value = def.value.transform(tt);
if (def.name.definition().id in in_use_ids) return true;
var w = {
name : def.name.name,
line : def.name.start.line,
col : def.name.start.col
};
- if (def.value && def.value.has_side_effects(compressor)) {
- def._unused_side_effects = true;
+ if (def.value && (def._unused_side_effects = def.value.drop_side_effect_free(compressor))) {
compressor.warn("Side effects in initialization of unused variable {name} [{file}:{line},{col}]", w);
return true;
}
for (var i = 0; i < def.length;) {
var x = def[i];
if (x._unused_side_effects) {
- side_effects.push(x.value);
+ side_effects.push(x._unused_side_effects);
def.splice(i, 1);
} else {
if (side_effects.length > 0) {
&& node.operator == "="
&& node.left instanceof AST_SymbolRef) {
var def = node.left.definition();
- if (!(def.id in in_use_ids) && self.variables.get(def.name) === def) {
- return node.right;
+ if (!(def.id in in_use_ids)
+ && self.variables.get(def.name) === def) {
+ return maintain_this_binding(tt.parent(), node, node.right.transform(tt));
}
}
if (node instanceof AST_For) {
def(AST_Constant, return_null);
def(AST_This, return_null);
def(AST_Call, function(compressor, first_in_statement){
- if (!this.has_pure_annotation(compressor) && compressor.pure_funcs(this)) return this;
+ if (!this.has_pure_annotation(compressor) && compressor.pure_funcs(this)) {
+ if (this.expression instanceof AST_Function
+ && (!this.expression.name || !this.expression.name.definition().references.length)) {
+ var node = this.clone();
+ node.expression = node.expression.process_expression(false);
+ return node;
+ }
+ return this;
+ }
+ if (this.pure) {
+ compressor.warn("Dropping __PURE__ call [{file}:{line},{col}]", this.start);
+ this.pure.value = this.pure.value.replace(/[@#]__PURE__/g, ' ');
+ }
var args = trim(this.args, compressor, first_in_statement);
return args && AST_Seq.from_array(args);
});
case "typeof":
if (this.expression instanceof AST_SymbolRef) return null;
default:
- if (first_in_statement && is_iife_call(this.expression)) return this;
- return this.expression.drop_side_effect_free(compressor, first_in_statement);
+ var expression = this.expression.drop_side_effect_free(compressor, first_in_statement);
+ if (first_in_statement
+ && this instanceof AST_UnaryPrefix
+ && is_iife_call(expression)) {
+ if (expression === this.expression && this.operator.length === 1) return this;
+ return make_node(AST_UnaryPrefix, this, {
+ operator: this.operator.length === 1 ? this.operator : "!",
+ expression: expression
+ });
+ }
+ return expression;
}
});
def(AST_SymbolRef, function() {
OPT(AST_SimpleStatement, function(self, compressor){
if (compressor.option("side_effects")) {
var body = self.body;
- if (!body.has_side_effects(compressor)) {
- compressor.warn("Dropping side-effect-free statement [{file}:{line},{col}]", self.start);
- return make_node(AST_EmptyStatement, self);
- }
var node = body.drop_side_effect_free(compressor, true);
if (!node) {
compressor.warn("Dropping side-effect-free statement [{file}:{line},{col}]", self.start);
}
} else {
// self instanceof AST_Do
- return self.body;
+ return self;
}
}
if (self instanceof AST_While) {
// 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.body = self.alternative || make_node(AST_EmptyStatement, self);
self.alternative = tmp;
}
if (is_empty(self.body) && is_empty(self.alternative)) {
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)
+ consequent : self.body.value || make_node(AST_Undefined, self.body),
+ alternative : self.alternative.value || make_node(AST_Undefined, self.alternative)
})
}).transform(compressor);
}
});
OPT(AST_Call, function(self, compressor){
+ var exp = self.expression;
+ if (compressor.option("reduce_vars")
+ && exp instanceof AST_SymbolRef) {
+ var def = exp.definition();
+ if (def.fixed instanceof AST_Defun) {
+ def.fixed = make_node(AST_Function, def.fixed, def.fixed).clone(true);
+ }
+ if (def.fixed instanceof AST_Function) {
+ exp = def.fixed;
+ if (compressor.option("unused")
+ && def.references.length == 1
+ && !(def.scope.uses_arguments
+ && def.orig[0] instanceof AST_SymbolFunarg)
+ && !def.scope.uses_eval
+ && compressor.find_parent(AST_Scope) === def.scope) {
+ self.expression = exp;
+ }
+ }
+ }
+ if (compressor.option("unused")
+ && exp instanceof AST_Function
+ && !exp.uses_arguments
+ && !exp.uses_eval) {
+ var pos = 0, last = 0;
+ for (var i = 0, len = self.args.length; i < len; i++) {
+ var trim = i >= exp.argnames.length;
+ if (trim || exp.argnames[i].__unused) {
+ var 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")) {
- var exp = self.expression;
if (exp instanceof AST_SymbolRef && exp.undeclared()) {
switch (exp.name) {
case "Array":
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: make_node(AST_UnaryPrefix, self, {
expression: self.args[0],
operator: "!"
}),
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 (exp instanceof AST_Function) {
+ if (exp.body[0] instanceof AST_Return) {
+ var value = exp.body[0].value;
+ if (!value || value.is_constant()) {
+ var args = self.args.concat(value || make_node(AST_Undefined, self));
+ return AST_Seq.from_array(args).transform(compressor);
+ }
+ }
+ if (compressor.option("side_effects")) {
+ if (!AST_Block.prototype.has_side_effects.call(exp, compressor)) {
+ var args = self.args.concat(make_node(AST_Undefined, self));
+ return AST_Seq.from_array(args).transform(compressor);
+ }
}
}
if (compressor.option("drop_console")) {
- if (self.expression instanceof AST_PropAccess) {
- var name = self.expression.expression;
+ if (exp instanceof AST_PropAccess) {
+ var name = exp.expression;
while (name.expression) {
name = name.expression;
}
self.car = self.car.drop_side_effect_free(compressor, first_in_statement(compressor));
if (!self.car) return maintain_this_binding(compressor.parent(), self, self.cdr);
if (compressor.option("cascade")) {
+ var left;
if (self.car instanceof AST_Assign
&& !self.car.left.has_side_effects(compressor)) {
- 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;
- }
+ left = self.car.left;
+ } else if (self.car instanceof AST_Unary
+ && (self.car.operator == "++" || self.car.operator == "--")) {
+ left = self.car.expression;
}
- if (!self.car.has_side_effects(compressor)
- && !self.cdr.has_side_effects(compressor)
- && self.car.equivalent_to(self.cdr)) {
- return self.car;
+ if (left) {
+ var parent, field;
+ var cdr = self.cdr;
+ while (true) {
+ if (cdr.equivalent_to(left)) {
+ var car = self.car instanceof AST_UnaryPostfix ? make_node(AST_UnaryPrefix, self.car, {
+ operator: self.car.operator,
+ expression: left
+ }) : self.car;
+ if (parent) {
+ parent[field] = car;
+ return self.cdr;
+ }
+ return car;
+ }
+ if (cdr instanceof AST_Binary && !(cdr instanceof AST_Assign)) {
+ field = cdr.left.is_constant() ? "right" : "left";
+ } else if (cdr instanceof AST_Call
+ || cdr instanceof AST_Unary && cdr.operator != "++" && cdr.operator != "--") {
+ field = "expression";
+ } else break;
+ parent = cdr;
+ cdr = cdr[field];
+ }
}
}
- if (self.cdr instanceof AST_UnaryPrefix
- && self.cdr.operator == "void"
- && !self.cdr.expression.has_side_effects(compressor)) {
- self.cdr.expression = self.car;
- return self.cdr;
- }
- if (self.cdr instanceof AST_Undefined) {
+ if (is_undefined(self.cdr)) {
return make_node(AST_UnaryPrefix, self, {
operator : "void",
expression : self.car
});
OPT(AST_UnaryPrefix, function(self, compressor){
- self = self.lift_sequences(compressor);
+ var seq = self.lift_sequences(compressor);
+ if (seq !== self) {
+ return seq;
+ }
var e = self.expression;
+ 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).transform(compressor);
+ }
+ }
if (compressor.option("booleans") && compressor.in_boolean_context()) {
switch (self.operator) {
case "!":
// 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);
- if (self.expression.has_side_effects(compressor)) {
- return make_node(AST_Seq, self, {
- car: self.expression,
- cdr: make_node(AST_True, self)
- });
- }
- return make_node(AST_True, self);
+ return make_node(AST_Seq, self, {
+ car: e,
+ cdr: make_node(AST_True, self)
+ }).optimize(compressor);
}
}
return self.evaluate(compressor)[0];
right: rhs[0]
}).optimize(compressor);
}
- function reverse(op, force) {
- if (force || !(self.left.has_side_effects(compressor) || self.right.has_side_effects(compressor))) {
+ function reversible() {
+ return self.left instanceof AST_Constant
+ || self.right instanceof AST_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;
if (!(self.left instanceof AST_Binary
&& PRECEDENCE[self.left.operator] >= PRECEDENCE[self.operator])) {
- reverse(null, true);
+ reverse();
}
}
if (/^[!=]==?$/.test(self.operator)) {
case "===":
case "!==":
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.operator = self.operator.substr(0, 2);
}
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);
- if (self.left.has_side_effects(compressor)) {
- return make_node(AST_Seq, self, {
- car: self.left,
- cdr: make_node(AST_False)
- }).optimize(compressor);
- }
- return make_node(AST_False, self);
+ return make_node(AST_Seq, self, {
+ car: self.left,
+ cdr: make_node(AST_False, self)
+ }).optimize(compressor);
}
if (ll.length > 1 && ll[1]) {
return rr[0];
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);
- if (self.left.has_side_effects(compressor)) {
- return make_node(AST_Seq, self, {
- car: self.left,
- cdr: make_node(AST_True)
- }).optimize(compressor);
- }
- return make_node(AST_True, self);
+ return make_node(AST_Seq, self, {
+ car: self.left,
+ cdr: make_node(AST_True, self)
+ }).optimize(compressor);
}
if (ll.length > 1 && !ll[1]) {
return rr[0];
case "+":
var ll = self.left.evaluate(compressor);
var rr = self.right.evaluate(compressor);
- if ((ll.length > 1 && ll[0] instanceof AST_String && ll[1] && !self.right.has_side_effects(compressor)) ||
- (rr.length > 1 && rr[0] instanceof AST_String && rr[1] && !self.left.has_side_effects(compressor))) {
+ if (ll.length > 1 && ll[0] instanceof AST_String && ll[1]) {
compressor.warn("+ in boolean context always true [{file}:{line},{col}]", self.start);
- return make_node(AST_True, self);
+ return make_node(AST_Seq, self, {
+ car: self.right,
+ cdr: make_node(AST_True, self)
+ }).optimize(compressor);
+ }
+ if (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_Seq, self, {
+ car: self.left,
+ cdr: make_node(AST_True, self)
+ }).optimize(compressor);
}
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 (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) {
}
break;
}
- if (self.operator == "+") {
+ 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.is_string(compressor)) {
self = make_node(AST_Binary, self, {
operator: "+",
- left: make_node(AST_String, null, {
+ 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 = make_node(AST_Binary, self, {
operator: "+",
left: self.left.left,
- right: make_node(AST_String, null, {
+ 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)
left: make_node(AST_Binary, self.left, {
operator: "+",
left: self.left.left,
- right: make_node(AST_String, null, {
+ 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
+ });
+ }
+ // -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
+ });
+ }
+ 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(reversed, self);
+ } else {
+ self = best_of(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
return def;
}
// testing against !self.scope.uses_with first is an optimization
- if (self.undeclared() && !isLHS(self, compressor.parent())
+ if (compressor.option("screw_ie8")
+ && self.undeclared()
+ && !isLHS(self, compressor.parent())
&& (!self.scope.uses_with || !compressor.find_parent(AST_With))) {
switch (self.name) {
case "undefined":
- return make_node(AST_Undefined, self);
+ return make_node(AST_Undefined, self).transform(compressor);
case "NaN":
return make_node(AST_NaN, self).transform(compressor);
case "Infinity":
}
if (compressor.option("evaluate") && compressor.option("reduce_vars")) {
var d = self.definition();
- if (!d.modified && d.init) {
+ if (d.fixed) {
if (d.should_replace === undefined) {
- var init = d.init.evaluate(compressor);
+ var init = d.fixed.evaluate(compressor);
if (init.length > 1) {
var value = init[0].print_to_string().length;
var name = d.name.length;
var scope = compressor.find_parent(AST_Scope);
var undef = scope.find_variable("undefined");
if (undef) {
- return make_node(AST_SymbolRef, self, {
+ var ref = make_node(AST_SymbolRef, self, {
name : "undefined",
scope : scope,
thedef : undef
});
+ ref.is_undefined = true;
+ return ref;
}
}
return self;
&& alternative instanceof AST_Assign
&& consequent.operator == alternative.operator
&& consequent.left.equivalent_to(alternative.left)
- && !consequent.left.has_side_effects(compressor)
+ && (!consequent.left.has_side_effects(compressor)
+ || !self.condition.has_side_effects(compressor))
) {
/*
* Stuff like this:
})
});
}
+ // x ? y(a) : y(b) --> y(x ? a : b)
if (consequent instanceof AST_Call
&& alternative.TYPE === consequent.TYPE
- && consequent.args.length == alternative.args.length
- && !consequent.expression.has_side_effects(compressor)
- && 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;
- }
+ && consequent.args.length == 1
+ && alternative.args.length == 1
+ && consequent.expression.equivalent_to(alternative.expression)
+ && !consequent.expression.has_side_effects(compressor)) {
+ 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
alternative: alternative
});
}
- // y?1:1 --> 1
- if (consequent.is_constant()
- && alternative.is_constant()
- && consequent.equivalent_to(alternative)) {
- var consequent_value = consequent.evaluate(compressor)[0];
- if (self.condition.has_side_effects(compressor)) {
- return AST_Seq.from_array([self.condition, consequent_value]);
- } else {
- return consequent_value;
- }
+ // x ? y : y --> x, y
+ if (consequent.equivalent_to(alternative)) {
+ return make_node(AST_Seq, self, {
+ car: self.condition,
+ cdr: consequent
+ }).optimize(compressor);
}
if (is_true(self.consequent)) {
});
function literals_in_boolean_context(self, compressor) {
- if (compressor.option("booleans") && compressor.in_boolean_context() && !self.has_side_effects(compressor)) {
- return make_node(AST_True, self);
+ if (compressor.option("booleans") && compressor.in_boolean_context()) {
+ var best = first_in_statement(compressor) ? best_of_statement : best_of;
+ return best(self, make_node(AST_Seq, self, {
+ car: self,
+ cdr: make_node(AST_True, self)
+ }).optimize(compressor));
}
return self;
};
OPT(AST_RegExp, literals_in_boolean_context);
OPT(AST_Return, function(self, compressor){
- if (self.value instanceof AST_Undefined) {
+ if (self.value && is_undefined(self.value)) {
self.value = null;
}
return self;
exports.base54 = base54;
exports.EXPECT_DIRECTIVE = EXPECT_DIRECTIVE;
exports.is_some_comments = is_some_comments;
-exports.is_comment5 = is_comment5;
exports.OutputStream = OutputStream;
exports.Compressor = Compressor;
exports.SourceMap = SourceMap;
return value;
}
-var fnPrefix = '!function(){';
-var fnSuffix = '}();';
-
function processOptions(options) {
['html5', 'includeAutoGeneratedTags'].forEach(function(key) {
if (!(key in options)) {
}
minifyJS.fromString = true;
(minifyJS.output || (minifyJS.output = {})).inline_script = true;
+ (minifyJS.parse || (minifyJS.parse = {})).bare_returns = false;
options.minifyJS = function(text, inline) {
var start = text.match(/^\s*<!--.*/);
var code = start ? text.slice(start[0].length).replace(/\n\s*-->\s*$/, '') : text;
try {
- if (inline) {
- code = fnPrefix + code + fnSuffix;
- }
+ minifyJS.parse.bare_returns = inline;
code = UglifyJS.minify(code, minifyJS).code;
- if (inline) {
- code = code.slice(fnPrefix.length, -fnSuffix.length);
- }
if (/;$/.test(code)) {
code = code.slice(0, -1);
}