Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cfde686eab | ||
|
|
a206964c0a | ||
|
|
c56d89f804 | ||
|
|
c215706350 | ||
|
|
d3b93ec682 | ||
|
|
6fe20dbe33 | ||
|
|
7ccdf3337b | ||
|
|
dafed54764 | ||
|
|
a84beafd1b | ||
|
|
f01cc1e413 |
@@ -773,7 +773,7 @@ var AST_Label = DEFNODE("Label", "references", {
|
||||
}
|
||||
}, AST_Symbol);
|
||||
|
||||
var AST_SymbolRef = DEFNODE("SymbolRef", null, {
|
||||
var AST_SymbolRef = DEFNODE("SymbolRef", "fixed", {
|
||||
$documentation: "Reference to some symbol (not definition/declaration)",
|
||||
}, AST_Symbol);
|
||||
|
||||
|
||||
312
lib/compress.js
312
lib/compress.js
@@ -551,6 +551,7 @@ merge(Compressor.prototype, {
|
||||
mark_assignment_to_arguments(sym);
|
||||
return;
|
||||
}
|
||||
if (sym.fixed) delete sym.fixed;
|
||||
var d = sym.definition();
|
||||
var safe = safe_to_assign(tw, d, sym.scope, node.right);
|
||||
d.assignments++;
|
||||
@@ -560,7 +561,7 @@ merge(Compressor.prototype, {
|
||||
var value = eq ? node.right : node;
|
||||
if (is_modified(compressor, tw, node, value, 0)) return;
|
||||
if (!eq) d.chained = true;
|
||||
d.fixed = eq ? function() {
|
||||
sym.fixed = d.fixed = eq ? function() {
|
||||
return node.right;
|
||||
} : function() {
|
||||
return make_node(AST_Binary, node, {
|
||||
@@ -574,7 +575,7 @@ merge(Compressor.prototype, {
|
||||
mark(tw, d, false);
|
||||
node.right.walk(tw);
|
||||
mark(tw, d, true);
|
||||
mark_escaped(tw, d, sym.scope, node, value, 0, 1);
|
||||
if (eq) mark_escaped(tw, d, sym.scope, node, value, 0, 1);
|
||||
return true;
|
||||
});
|
||||
def(AST_Binary, function(tw) {
|
||||
@@ -730,6 +731,7 @@ merge(Compressor.prototype, {
|
||||
this.definition().fixed = false;
|
||||
});
|
||||
def(AST_SymbolRef, function(tw, descend, compressor) {
|
||||
if (this.fixed) delete this.fixed;
|
||||
var d = this.definition();
|
||||
d.references.push(this);
|
||||
if (d.references.length == 1
|
||||
@@ -742,7 +744,7 @@ merge(Compressor.prototype, {
|
||||
d.fixed = false;
|
||||
} else if (d.fixed) {
|
||||
value = this.fixed_value();
|
||||
if (value instanceof AST_Lambda && recursive_ref(tw, d)) {
|
||||
if (recursive_ref(tw, d)) {
|
||||
d.recursive_refs++;
|
||||
} else if (value && ref_once(tw, compressor, d)) {
|
||||
d.single_use = value instanceof AST_Lambda && !value.pinned()
|
||||
@@ -797,13 +799,14 @@ merge(Compressor.prototype, {
|
||||
mark_assignment_to_arguments(exp);
|
||||
return;
|
||||
}
|
||||
if (exp.fixed) delete exp.fixed;
|
||||
var d = exp.definition();
|
||||
var safe = safe_to_assign(tw, d, exp.scope, true);
|
||||
d.assignments++;
|
||||
var fixed = d.fixed;
|
||||
if (!fixed) return;
|
||||
d.chained = true;
|
||||
d.fixed = function() {
|
||||
exp.fixed = d.fixed = function() {
|
||||
return make_node(AST_Binary, node, {
|
||||
operator: node.operator.slice(0, -1),
|
||||
left: make_node(AST_UnaryPrefix, node, {
|
||||
@@ -874,10 +877,11 @@ merge(Compressor.prototype, {
|
||||
this.walk(tw);
|
||||
});
|
||||
|
||||
AST_Symbol.DEFMETHOD("fixed_value", function() {
|
||||
AST_Symbol.DEFMETHOD("fixed_value", function(final) {
|
||||
var fixed = this.definition().fixed;
|
||||
if (!fixed || fixed instanceof AST_Node) return fixed;
|
||||
return fixed();
|
||||
if (!fixed) return fixed;
|
||||
if (!final && this.fixed) fixed = this.fixed;
|
||||
return fixed instanceof AST_Node ? fixed : fixed();
|
||||
});
|
||||
|
||||
AST_SymbolRef.DEFMETHOD("is_immutable", function() {
|
||||
@@ -1287,7 +1291,9 @@ merge(Compressor.prototype, {
|
||||
if (node instanceof AST_Try) return true;
|
||||
if (node instanceof AST_With) return true;
|
||||
if (replace_all) return false;
|
||||
return node instanceof AST_SymbolRef && !node.is_declared(compressor);
|
||||
return node instanceof AST_SymbolRef
|
||||
&& !node.is_declared(compressor)
|
||||
&& !(parent instanceof AST_Assign && parent.left === node);
|
||||
}
|
||||
|
||||
function in_conditional(node, parent) {
|
||||
@@ -2290,17 +2296,14 @@ merge(Compressor.prototype, {
|
||||
def(AST_Node, is_strict);
|
||||
def(AST_Array, return_false);
|
||||
def(AST_Assign, function(compressor) {
|
||||
return this.operator == "="
|
||||
&& this.right._dot_throw(compressor);
|
||||
})
|
||||
return this.operator == "=" && this.right._dot_throw(compressor);
|
||||
});
|
||||
def(AST_Binary, function(compressor) {
|
||||
return (this.operator == "&&" || this.operator == "||")
|
||||
&& (this.left._dot_throw(compressor) || this.right._dot_throw(compressor));
|
||||
})
|
||||
return lazy_op[this.operator] && (this.left._dot_throw(compressor) || this.right._dot_throw(compressor));
|
||||
});
|
||||
def(AST_Conditional, function(compressor) {
|
||||
return this.consequent._dot_throw(compressor)
|
||||
|| this.alternative._dot_throw(compressor);
|
||||
})
|
||||
return this.consequent._dot_throw(compressor) || this.alternative._dot_throw(compressor);
|
||||
});
|
||||
def(AST_Constant, return_false);
|
||||
def(AST_Dot, function(compressor) {
|
||||
if (!is_strict(compressor)) return false;
|
||||
@@ -2340,6 +2343,51 @@ merge(Compressor.prototype, {
|
||||
node.DEFMETHOD("_dot_throw", func);
|
||||
});
|
||||
|
||||
(function(def) {
|
||||
def(AST_Node, return_false);
|
||||
def(AST_Array, return_true);
|
||||
def(AST_Assign, function(compressor) {
|
||||
return this.operator != "=" || this.right.is_defined(compressor);
|
||||
});
|
||||
def(AST_Binary, function(compressor) {
|
||||
switch (this.operator) {
|
||||
case "&&":
|
||||
return this.left.is_defined(compressor) && this.right.is_defined(compressor);
|
||||
case "||":
|
||||
return this.left.is_defined(compressor) || this.right.is_defined(compressor);
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
});
|
||||
def(AST_Conditional, function(compressor) {
|
||||
return this.consequent.is_defined(compressor) && this.alternative.is_defined(compressor);
|
||||
});
|
||||
def(AST_Constant, return_true);
|
||||
def(AST_Lambda, return_true);
|
||||
def(AST_Object, return_true);
|
||||
def(AST_Sequence, function(compressor) {
|
||||
return this.tail_node().is_defined(compressor);
|
||||
});
|
||||
def(AST_SymbolRef, function(compressor) {
|
||||
if (this.is_undefined) return false;
|
||||
if (is_undeclared_ref(this) && this.is_declared(compressor)) return true;
|
||||
if (this.is_immutable()) return true;
|
||||
var fixed = this.fixed_value();
|
||||
if (!fixed) return false;
|
||||
this.is_defined = return_true;
|
||||
var result = fixed.is_defined(compressor);
|
||||
delete this.is_defined;
|
||||
return result;
|
||||
});
|
||||
def(AST_UnaryPrefix, function() {
|
||||
return this.operator != "void";
|
||||
});
|
||||
def(AST_UnaryPostfix, return_true);
|
||||
def(AST_Undefined, return_false);
|
||||
})(function(node, func) {
|
||||
node.DEFMETHOD("is_defined", func);
|
||||
});
|
||||
|
||||
/* -----[ boolean/negation helpers ]----- */
|
||||
|
||||
// methods to determine whether an expression has a boolean result type
|
||||
@@ -2484,6 +2532,20 @@ merge(Compressor.prototype, {
|
||||
return this.operator == "+" &&
|
||||
(this.left.is_string(compressor) || this.right.is_string(compressor));
|
||||
});
|
||||
var fn = makePredicate([
|
||||
"charAt",
|
||||
"substr",
|
||||
"substring",
|
||||
"toLowerCase",
|
||||
"toString",
|
||||
"toUpperCase",
|
||||
"trim",
|
||||
]);
|
||||
def(AST_Call, function(compressor) {
|
||||
if (!compressor.option("unsafe")) return false;
|
||||
var exp = this.expression;
|
||||
return exp instanceof AST_Dot && fn[exp.property];
|
||||
});
|
||||
def(AST_Conditional, function(compressor) {
|
||||
return this.consequent.is_string(compressor) && this.alternative.is_string(compressor);
|
||||
});
|
||||
@@ -2491,6 +2553,9 @@ merge(Compressor.prototype, {
|
||||
return this.tail_node().is_string(compressor);
|
||||
});
|
||||
def(AST_String, return_true);
|
||||
def(AST_Sub, function(compressor) {
|
||||
return this.expression.is_string(compressor) && this.property instanceof AST_Number;
|
||||
});
|
||||
def(AST_SymbolRef, function(compressor) {
|
||||
var fixed = this.fixed_value();
|
||||
if (!fixed) return false;
|
||||
@@ -2597,9 +2662,9 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
|
||||
function convert_to_predicate(obj) {
|
||||
for (var key in obj) {
|
||||
Object.keys(obj).forEach(function(key) {
|
||||
obj[key] = makePredicate(obj[key]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var object_fns = [
|
||||
@@ -2970,6 +3035,7 @@ merge(Compressor.prototype, {
|
||||
if (arg === value) return this;
|
||||
args.push(value);
|
||||
}
|
||||
if (key == "replace" && typeof args[1] == "function") return this;
|
||||
try {
|
||||
return val[key].apply(val, args);
|
||||
} catch (ex) {
|
||||
@@ -3078,24 +3144,31 @@ merge(Compressor.prototype, {
|
||||
return this.pure || !compressor.pure_funcs(this);
|
||||
});
|
||||
AST_Node.DEFMETHOD("is_call_pure", return_false);
|
||||
AST_Dot.DEFMETHOD("is_call_pure", function(compressor) {
|
||||
if (!compressor.option("unsafe")) return;
|
||||
var expr = this.expression;
|
||||
AST_Call.DEFMETHOD("is_call_pure", function(compressor) {
|
||||
if (!compressor.option("unsafe")) return false;
|
||||
var dot = this.expression;
|
||||
if (!(dot instanceof AST_Dot)) return false;
|
||||
var exp = dot.expression;
|
||||
var map;
|
||||
if (expr instanceof AST_Array) {
|
||||
var prop = dot.property;
|
||||
if (exp instanceof AST_Array) {
|
||||
map = native_fns.Array;
|
||||
} else if (expr.is_boolean(compressor)) {
|
||||
} else if (exp.is_boolean(compressor)) {
|
||||
map = native_fns.Boolean;
|
||||
} else if (expr.is_number(compressor)) {
|
||||
} else if (exp.is_number(compressor)) {
|
||||
map = native_fns.Number;
|
||||
} else if (expr instanceof AST_RegExp) {
|
||||
} else if (exp instanceof AST_RegExp) {
|
||||
map = native_fns.RegExp;
|
||||
} else if (expr.is_string(compressor)) {
|
||||
} else if (exp.is_string(compressor)) {
|
||||
map = native_fns.String;
|
||||
} else if (!this.may_throw_on_access(compressor)) {
|
||||
if (prop == "replace") {
|
||||
var arg = this.args[1];
|
||||
if (arg && !arg.is_string(compressor)) return false;
|
||||
}
|
||||
} else if (!dot.may_throw_on_access(compressor)) {
|
||||
map = native_fns.Object;
|
||||
}
|
||||
return map && map[this.property];
|
||||
return map && map[prop];
|
||||
});
|
||||
|
||||
// determine if expression has side effects
|
||||
@@ -3120,8 +3193,7 @@ merge(Compressor.prototype, {
|
||||
});
|
||||
def(AST_Call, function(compressor) {
|
||||
if (!this.is_expr_pure(compressor)
|
||||
&& (!this.expression.is_call_pure(compressor)
|
||||
|| this.expression.has_side_effects(compressor))) {
|
||||
&& (!this.is_call_pure(compressor) || this.expression.has_side_effects(compressor))) {
|
||||
return true;
|
||||
}
|
||||
return any(this.args, compressor);
|
||||
@@ -3512,7 +3584,7 @@ merge(Compressor.prototype, {
|
||||
if (def.value.has_side_effects(compressor)) {
|
||||
def.value.walk(tw);
|
||||
}
|
||||
if (!node_def.chained && def.name.fixed_value() === def.value) {
|
||||
if (!node_def.chained && def.name.fixed_value(true) === def.value) {
|
||||
fixed_ids[node_def.id] = def;
|
||||
}
|
||||
}
|
||||
@@ -3783,7 +3855,7 @@ merge(Compressor.prototype, {
|
||||
if (node instanceof AST_Assign) {
|
||||
node.right.walk(tw);
|
||||
if (node.left === sym) {
|
||||
if (!node_def.chained && sym.fixed_value() === node.right) {
|
||||
if (!node_def.chained && sym.fixed_value(true) === node.right) {
|
||||
fixed_ids[node_def.id] = node;
|
||||
}
|
||||
if (!node.write_only) {
|
||||
@@ -4100,10 +4172,18 @@ merge(Compressor.prototype, {
|
||||
var right = this.right.drop_side_effect_free(compressor, first_in_statement);
|
||||
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.drop_side_effect_free(compressor);
|
||||
return node;
|
||||
var node;
|
||||
if (right === this.right) {
|
||||
node = this;
|
||||
} else {
|
||||
node = this.clone();
|
||||
node.right = right.drop_side_effect_free(compressor);
|
||||
}
|
||||
return (first_in_statement ? best_of_statement : best_of_expression)(node, make_node(AST_Binary, this, {
|
||||
operator: node.operator == "&&" ? "||" : "&&",
|
||||
left: node.left.negate(compressor, first_in_statement),
|
||||
right: node.right
|
||||
}));
|
||||
} else {
|
||||
var left = this.left.drop_side_effect_free(compressor, first_in_statement);
|
||||
if (!left) return right;
|
||||
@@ -4112,16 +4192,15 @@ merge(Compressor.prototype, {
|
||||
});
|
||||
def(AST_Call, function(compressor, first_in_statement) {
|
||||
if (!this.is_expr_pure(compressor)) {
|
||||
if (this.expression.is_call_pure(compressor)) {
|
||||
var exp = this.expression;
|
||||
if (this.is_call_pure(compressor)) {
|
||||
var exprs = this.args.slice();
|
||||
exprs.unshift(this.expression.expression);
|
||||
exprs.unshift(exp.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)) {
|
||||
if (exp instanceof AST_Function && (!exp.name || !exp.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) {
|
||||
@@ -4913,15 +4992,21 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
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);
|
||||
}
|
||||
if (self.args.length < 2) {
|
||||
var node = make_node(AST_Sub, self, {
|
||||
expression: exp.expression,
|
||||
property: self.args.length ? make_node(AST_Binary, self.args[0], {
|
||||
operator: "|",
|
||||
left: make_node(AST_Number, self, {
|
||||
value: 0
|
||||
}),
|
||||
right: self.args[0]
|
||||
}) : make_node(AST_Number, self, {
|
||||
value: 0
|
||||
})
|
||||
});
|
||||
node.is_string = return_true;
|
||||
return node.optimize(compressor);
|
||||
}
|
||||
break;
|
||||
case "apply":
|
||||
@@ -5102,7 +5187,7 @@ merge(Compressor.prototype, {
|
||||
})) {
|
||||
return false;
|
||||
}
|
||||
} else if (line instanceof AST_EmptyStatement) {
|
||||
} else if (line instanceof AST_Defun || line instanceof AST_EmptyStatement) {
|
||||
continue;
|
||||
} else if (stat) {
|
||||
return false;
|
||||
@@ -5113,16 +5198,15 @@ merge(Compressor.prototype, {
|
||||
return return_value(stat);
|
||||
}
|
||||
|
||||
function var_exists(catches, name) {
|
||||
return catches[name] || identifier_atom[name] || scope.var_names()[name];
|
||||
}
|
||||
|
||||
function can_inject_args(catches, safe_to_inject) {
|
||||
for (var i = 0; i < fn.argnames.length; 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 (!safe_to_inject || var_exists(catches, arg.name)) return false;
|
||||
if (in_loop) in_loop.push(arg.definition());
|
||||
}
|
||||
return true;
|
||||
@@ -5131,15 +5215,15 @@ merge(Compressor.prototype, {
|
||||
function can_inject_vars(catches, safe_to_inject) {
|
||||
for (var i = 0; i < fn.body.length; i++) {
|
||||
var stat = fn.body[i];
|
||||
if (stat instanceof AST_Defun) {
|
||||
if (!safe_to_inject || var_exists(catches, stat.name.name)) return false;
|
||||
continue;
|
||||
}
|
||||
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 (var_exists(catches, name.name)) return false;
|
||||
if (in_loop) in_loop.push(name.definition());
|
||||
}
|
||||
}
|
||||
@@ -5243,12 +5327,21 @@ merge(Compressor.prototype, {
|
||||
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
|
||||
}));
|
||||
}
|
||||
var args = fn.body.filter(function(stat) {
|
||||
if (stat instanceof AST_Defun) {
|
||||
var def = stat.name.definition();
|
||||
scope.functions.set(def.name, def);
|
||||
scope.variables.set(def.name, def);
|
||||
scope.enclosed.push(def);
|
||||
scope.var_names()[def.name] = true;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
args.unshift(scope.body.indexOf(compressor.parent(level - 1)) + 1, 0);
|
||||
if (decls.length) args.push(make_node(AST_Var, fn, {
|
||||
definitions: decls
|
||||
}));
|
||||
[].splice.apply(scope.body, args);
|
||||
return expressions;
|
||||
}
|
||||
});
|
||||
@@ -5437,6 +5530,7 @@ merge(Compressor.prototype, {
|
||||
return this;
|
||||
});
|
||||
|
||||
var indexFns = makePredicate("indexOf lastIndexOf");
|
||||
var commutativeOperators = makePredicate("== === != !== * & | ^");
|
||||
function is_object(node) {
|
||||
return node instanceof AST_Array
|
||||
@@ -5476,6 +5570,13 @@ merge(Compressor.prototype, {
|
||||
if (compressor.option("comparisons")) switch (self.operator) {
|
||||
case "===":
|
||||
case "!==":
|
||||
if (is_undefined(self.left, compressor) && self.right.is_defined(compressor)) {
|
||||
compressor.warn("Expression always defined [{file}:{line},{col}]", self.start);
|
||||
return make_sequence(self, [
|
||||
self.right,
|
||||
make_node(self.operator == "===" ? AST_False : AST_True, self)
|
||||
]).optimize(compressor);
|
||||
}
|
||||
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)) ||
|
||||
@@ -5514,6 +5615,8 @@ merge(Compressor.prototype, {
|
||||
break;
|
||||
case "&&":
|
||||
case "||":
|
||||
// void 0 !== x && null !== x => null != x
|
||||
// void 0 === x || null === x => null == x
|
||||
var lhs = self.left;
|
||||
if (lhs.operator == self.operator) {
|
||||
lhs = lhs.right;
|
||||
@@ -5542,7 +5645,8 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (compressor.option("booleans") && self.operator == "+" && compressor.in_boolean_context()) {
|
||||
if (compressor.option("booleans") && compressor.in_boolean_context()) switch (self.operator) {
|
||||
case "+":
|
||||
var ll = self.left.evaluate(compressor);
|
||||
var rr = self.right.evaluate(compressor);
|
||||
if (ll && typeof ll == "string") {
|
||||
@@ -5559,6 +5663,20 @@ merge(Compressor.prototype, {
|
||||
make_node(AST_True, self)
|
||||
]).optimize(compressor);
|
||||
}
|
||||
break;
|
||||
case "==":
|
||||
if (self.left instanceof AST_String && self.left.getValue() == "" && self.right.is_string(compressor)) {
|
||||
return make_node(AST_UnaryPrefix, self, {
|
||||
operator: "!",
|
||||
expression: self.right
|
||||
}).optimize(compressor);
|
||||
}
|
||||
break;
|
||||
case "!=":
|
||||
if (self.left instanceof AST_String && self.left.getValue() == "" && self.right.is_string(compressor)) {
|
||||
return self.right.optimize(compressor);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (compressor.option("comparisons") && self.is_boolean(compressor)) {
|
||||
if (!(compressor.parent() instanceof AST_Binary)
|
||||
@@ -5578,12 +5696,12 @@ merge(Compressor.prototype, {
|
||||
if (self.right instanceof AST_String
|
||||
&& self.right.getValue() == ""
|
||||
&& self.left.is_string(compressor)) {
|
||||
return self.left;
|
||||
return self.left.optimize(compressor);
|
||||
}
|
||||
if (self.left instanceof AST_String
|
||||
&& self.left.getValue() == ""
|
||||
&& self.right.is_string(compressor)) {
|
||||
return self.right;
|
||||
return self.right.optimize(compressor);
|
||||
}
|
||||
if (self.left instanceof AST_Binary
|
||||
&& self.left.operator == "+"
|
||||
@@ -5591,7 +5709,7 @@ merge(Compressor.prototype, {
|
||||
&& self.left.left.getValue() == ""
|
||||
&& self.right.is_string(compressor)) {
|
||||
self.left = self.left.right;
|
||||
return self.transform(compressor);
|
||||
return self.optimize(compressor);
|
||||
}
|
||||
}
|
||||
if (compressor.option("evaluate")) {
|
||||
@@ -5846,6 +5964,35 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (compressor.option("unsafe")
|
||||
&& self.right instanceof AST_Call
|
||||
&& self.right.expression instanceof AST_Dot
|
||||
&& indexFns[self.right.expression.property]) {
|
||||
if (compressor.option("booleans")
|
||||
&& (self.operator == "==" || self.operator == "!=")
|
||||
&& self.left instanceof AST_Number
|
||||
&& self.left.getValue() == 0
|
||||
&& compressor.in_boolean_context()) {
|
||||
return (self.operator == "==" ? make_node(AST_UnaryPrefix, self, {
|
||||
operator: "!",
|
||||
expression: self.right
|
||||
}) : self.right).optimize(compressor);
|
||||
}
|
||||
if (compressor.option("comparisons") && is_indexOf_match_pattern()) {
|
||||
var node = make_node(AST_UnaryPrefix, self, {
|
||||
operator: "!",
|
||||
expression: make_node(AST_UnaryPrefix, self, {
|
||||
operator: "~",
|
||||
expression: self.right
|
||||
})
|
||||
});
|
||||
if (self.operator == "!=" || self.operator == "<=") node = make_node(AST_UnaryPrefix, self, {
|
||||
operator: "!",
|
||||
expression: node
|
||||
});
|
||||
return node.optimize(compressor);
|
||||
}
|
||||
}
|
||||
// x && (y && z) ==> x && y && z
|
||||
// x || (y || z) ==> x || y || z
|
||||
// x + ("y" + z) ==> x + "y" + z
|
||||
@@ -5879,6 +6026,23 @@ merge(Compressor.prototype, {
|
||||
if (node.is_truthy()) return true;
|
||||
return node.evaluate(compressor);
|
||||
}
|
||||
|
||||
function is_indexOf_match_pattern() {
|
||||
switch (self.operator) {
|
||||
case ">":
|
||||
case "<=":
|
||||
// 0 > array.indexOf(string) => !~array.indexOf(string)
|
||||
// 0 <= array.indexOf(string) => !!~array.indexOf(string)
|
||||
return self.left instanceof AST_Number && self.left.getValue() == 0;
|
||||
case "==":
|
||||
case "!=":
|
||||
// -1 == array.indexOf(string) => !~array.indexOf(string)
|
||||
// -1 != array.indexOf(string) => !!~array.indexOf(string)
|
||||
return self.left instanceof AST_Number && self.left.getValue() == -1
|
||||
|| self.left instanceof AST_UnaryPrefix && self.left.operator == "-"
|
||||
&& self.left.expression instanceof AST_Number && self.left.expression.getValue() == 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function recursive_ref(compressor, def) {
|
||||
@@ -6125,7 +6289,7 @@ merge(Compressor.prototype, {
|
||||
if (parent instanceof AST_Exit) {
|
||||
if (in_try(level, parent)) break;
|
||||
if (is_reachable(def.scope, [ def ])) break;
|
||||
if (self.operator == "=") return self.right;
|
||||
if (self.operator == "=") return self.right.optimize(compressor);
|
||||
def.fixed = false;
|
||||
return make_node(AST_Binary, self, {
|
||||
operator: self.operator.slice(0, -1),
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"description": "JavaScript parser, mangler/compressor and beautifier toolkit",
|
||||
"author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
|
||||
"license": "BSD-2-Clause",
|
||||
"version": "3.5.6",
|
||||
"version": "3.5.8",
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
},
|
||||
|
||||
@@ -289,3 +289,25 @@ increment_decrement_2: {
|
||||
}
|
||||
expect_stdout: "42"
|
||||
}
|
||||
|
||||
issue_3375: {
|
||||
options = {
|
||||
assignments: true,
|
||||
reduce_vars: true,
|
||||
}
|
||||
input: {
|
||||
console.log(typeof function(b) {
|
||||
var a = b += 1;
|
||||
--b;
|
||||
return a;
|
||||
}("object"));
|
||||
}
|
||||
expect: {
|
||||
console.log(typeof function(b) {
|
||||
var a = b += 1;
|
||||
--b;
|
||||
return a;
|
||||
}("object"));
|
||||
}
|
||||
expect_stdout: "string"
|
||||
}
|
||||
|
||||
@@ -6157,3 +6157,24 @@ sub_property: {
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
}
|
||||
|
||||
assign_undeclared: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
toplevel: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
var A = (console.log(42), function() {});
|
||||
B = new A();
|
||||
console.log(typeof B);
|
||||
}
|
||||
expect: {
|
||||
B = new (console.log(42), function() {})();
|
||||
console.log(typeof B);
|
||||
}
|
||||
expect_stdout: [
|
||||
"42",
|
||||
"object",
|
||||
]
|
||||
}
|
||||
|
||||
@@ -345,3 +345,38 @@ is_boolean_var: {
|
||||
}
|
||||
expect_stdout: "1"
|
||||
}
|
||||
|
||||
is_defined: {
|
||||
options = {
|
||||
comparisons: true,
|
||||
}
|
||||
input: {
|
||||
console.log(function a() {
|
||||
return void 0 === a;
|
||||
}());
|
||||
}
|
||||
expect: {
|
||||
console.log(function a() {
|
||||
return a, false;
|
||||
}());
|
||||
}
|
||||
expect_stdout: "false"
|
||||
expect_warnings: [
|
||||
"WARN: Expression always defined [test/compress/comparisons.js:2,19]",
|
||||
]
|
||||
}
|
||||
|
||||
unsafe_indexOf: {
|
||||
options = {
|
||||
booleans: true,
|
||||
comparisons: true,
|
||||
unsafe: true,
|
||||
}
|
||||
input: {
|
||||
if (Object.keys({ foo: 42 }).indexOf("foo") >= 0) console.log("PASS");
|
||||
}
|
||||
expect: {
|
||||
if (~Object.keys({ foo: 42 }).indexOf("foo")) console.log("PASS");
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
}
|
||||
|
||||
@@ -1416,3 +1416,22 @@ issue_3271: {
|
||||
}
|
||||
expect_stdout: "1 1"
|
||||
}
|
||||
|
||||
iife_condition: {
|
||||
options = {
|
||||
conditionals: true,
|
||||
side_effects: true,
|
||||
}
|
||||
input: {
|
||||
if (function() {
|
||||
return console;
|
||||
}())
|
||||
console.log("PASS");
|
||||
}
|
||||
expect: {
|
||||
!function() {
|
||||
return console;
|
||||
}() || console.log("PASS");
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
}
|
||||
|
||||
@@ -942,3 +942,21 @@ issue_2929: {
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
}
|
||||
|
||||
unsafe_string_replace: {
|
||||
options = {
|
||||
side_effects: true,
|
||||
unsafe: true,
|
||||
}
|
||||
input: {
|
||||
"foo".replace("f", function() {
|
||||
console.log("PASS");
|
||||
});
|
||||
}
|
||||
expect: {
|
||||
"foo".replace("f", function() {
|
||||
console.log("PASS");
|
||||
});
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
}
|
||||
|
||||
@@ -2005,3 +2005,24 @@ issue_3233: {
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
}
|
||||
|
||||
issue_3375: {
|
||||
options = {
|
||||
reduce_vars: true,
|
||||
toplevel: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
var b = 1;
|
||||
var a = c = [], c = --b + ("function" == typeof f && f());
|
||||
var a = c && c[a];
|
||||
console.log(a, b);
|
||||
}
|
||||
expect: {
|
||||
var b = 1;
|
||||
var a = [], c = --b + ("function" == typeof f && f());
|
||||
a = c && c[a];
|
||||
console.log(a, b);
|
||||
}
|
||||
expect_stdout: "0 0"
|
||||
}
|
||||
|
||||
@@ -820,8 +820,8 @@ unsafe_charAt_noop: {
|
||||
}
|
||||
expect: {
|
||||
console.log(
|
||||
s.charAt(0),
|
||||
"string".charAt(x),
|
||||
s[0],
|
||||
"string"[0 | x],
|
||||
(typeof x)[0]
|
||||
);
|
||||
}
|
||||
@@ -1712,3 +1712,21 @@ unsafe_escaped: {
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
}
|
||||
|
||||
unsafe_string_replace: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
unsafe: true,
|
||||
}
|
||||
input: {
|
||||
"foo".replace("f", function() {
|
||||
console.log("PASS");
|
||||
});
|
||||
}
|
||||
expect: {
|
||||
"foo".replace("f", function() {
|
||||
console.log("PASS");
|
||||
});
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
}
|
||||
|
||||
@@ -3003,12 +3003,10 @@ issue_3366: {
|
||||
f();
|
||||
}
|
||||
expect: {
|
||||
(function() {
|
||||
function a() {}
|
||||
(function() {
|
||||
this && a && console.log("PASS");
|
||||
})();
|
||||
})();
|
||||
void function() {
|
||||
this && a && console.log("PASS");
|
||||
}();
|
||||
function a() {}
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
}
|
||||
@@ -3041,3 +3039,29 @@ issue_3371: {
|
||||
}
|
||||
expect_stdout: "function"
|
||||
}
|
||||
|
||||
class_iife: {
|
||||
options = {
|
||||
inline: true,
|
||||
sequences: true,
|
||||
toplevel: true,
|
||||
}
|
||||
input: {
|
||||
var A = function() {
|
||||
function B() {}
|
||||
B.prototype.m = function() {
|
||||
console.log("PASS");
|
||||
};
|
||||
return B;
|
||||
}();
|
||||
new A().m();
|
||||
}
|
||||
expect: {
|
||||
var A = (B.prototype.m = function() {
|
||||
console.log("PASS");
|
||||
}, B);
|
||||
function B() {}
|
||||
new A().m();
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
issue_1639_1: {
|
||||
options = {
|
||||
booleans: true,
|
||||
@@ -12,7 +11,6 @@ issue_1639_1: {
|
||||
}
|
||||
input: {
|
||||
var a = 100, b = 10;
|
||||
|
||||
var L1 = 5;
|
||||
while (--L1 > 0) {
|
||||
if ((--b), false) {
|
||||
@@ -21,7 +19,6 @@ issue_1639_1: {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(a, b);
|
||||
}
|
||||
expect: {
|
||||
@@ -29,7 +26,7 @@ issue_1639_1: {
|
||||
if (--b, 0) var ignore = 0;
|
||||
console.log(a, b);
|
||||
}
|
||||
expect_stdout: true
|
||||
expect_stdout: "100 6"
|
||||
}
|
||||
|
||||
issue_1639_2: {
|
||||
@@ -44,25 +41,23 @@ issue_1639_2: {
|
||||
}
|
||||
input: {
|
||||
var a = 100, b = 10;
|
||||
|
||||
function f19() {
|
||||
if (++a, false)
|
||||
if (a)
|
||||
if (++a);
|
||||
}
|
||||
f19();
|
||||
|
||||
console.log(a, b);
|
||||
}
|
||||
expect: {
|
||||
var a = 100, b = 10;
|
||||
function f19() {
|
||||
++a, 0;
|
||||
++a, 1;
|
||||
}
|
||||
f19(),
|
||||
console.log(a, b);
|
||||
}
|
||||
expect_stdout: true
|
||||
expect_stdout: "101 10"
|
||||
}
|
||||
|
||||
issue_1639_3: {
|
||||
@@ -84,5 +79,5 @@ issue_1639_3: {
|
||||
a++,
|
||||
console.log(a, b);
|
||||
}
|
||||
expect_stdout: true
|
||||
expect_stdout: "101 10"
|
||||
}
|
||||
|
||||
@@ -6737,3 +6737,21 @@ drop_side_effect_free: {
|
||||
}
|
||||
expect_stdout: "123"
|
||||
}
|
||||
|
||||
issue_3377: {
|
||||
options = {
|
||||
reduce_vars: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
console.log(function f() {
|
||||
return f[0], (f = 42);
|
||||
}());
|
||||
}
|
||||
expect: {
|
||||
console.log(function f() {
|
||||
return f[0], (f = 42);
|
||||
}());
|
||||
}
|
||||
expect_stdout: "42"
|
||||
}
|
||||
|
||||
@@ -702,7 +702,7 @@ side_effects_cascade_3: {
|
||||
}
|
||||
expect: {
|
||||
function f(a, b) {
|
||||
!(b += a) && ((b = a) || (b -= a, b ^= a)),
|
||||
(b += a) || (b = a) || (b -= a, b ^= a),
|
||||
a--;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user