Merge branch 'master' into harmony
This commit is contained in:
198
lib/compress.js
198
lib/compress.js
@@ -152,6 +152,12 @@ merge(Compressor.prototype, {
|
||||
|
||||
AST_Node.DEFMETHOD("clear_opt_flags", function(){
|
||||
this.walk(new TreeWalker(function(node){
|
||||
if (node instanceof AST_SymbolRef) {
|
||||
var d = node.definition();
|
||||
if (d && d.init) {
|
||||
delete d.init._evaluated;
|
||||
}
|
||||
}
|
||||
if (!(node instanceof AST_Directive || node instanceof AST_Constant)) {
|
||||
node._squeezed = false;
|
||||
node._optimized = false;
|
||||
@@ -349,7 +355,7 @@ merge(Compressor.prototype, {
|
||||
if (ref.scope.uses_eval || ref.scope.uses_with) break;
|
||||
|
||||
// Constant single use vars can be replaced in any scope.
|
||||
if (!(var_decl.value instanceof AST_RegExp) && var_decl.value.is_constant(compressor)) {
|
||||
if (var_decl.value.is_constant()) {
|
||||
var ctt = new TreeTransformer(function(node) {
|
||||
if (node === ref)
|
||||
return replace_var(node, ctt.parent(), true);
|
||||
@@ -989,6 +995,11 @@ merge(Compressor.prototype, {
|
||||
node.DEFMETHOD("is_string", func);
|
||||
});
|
||||
|
||||
function isLHS(node, parent) {
|
||||
return parent instanceof AST_Unary && (parent.operator === "++" || parent.operator === "--")
|
||||
|| parent instanceof AST_Assign && parent.left === node;
|
||||
}
|
||||
|
||||
function best_of(ast1, ast2) {
|
||||
return ast1.print_to_string().length >
|
||||
ast2.print_to_string().length
|
||||
@@ -1006,39 +1017,61 @@ merge(Compressor.prototype, {
|
||||
// constant; otherwise it's the original or a replacement node.
|
||||
AST_Node.DEFMETHOD("evaluate", function(compressor){
|
||||
if (!compressor.option("evaluate")) return [ this ];
|
||||
var val;
|
||||
try {
|
||||
var val = this._eval(compressor);
|
||||
return [ best_of(make_node_from_constant(compressor, val, this), this), val ];
|
||||
val = this._eval(compressor);
|
||||
} catch(ex) {
|
||||
if (ex !== def) throw ex;
|
||||
return [ this ];
|
||||
}
|
||||
var node;
|
||||
try {
|
||||
node = make_node_from_constant(compressor, val, this);
|
||||
} catch(ex) {
|
||||
return [ this ];
|
||||
}
|
||||
return [ best_of(node, this), val ];
|
||||
});
|
||||
AST_Node.DEFMETHOD("is_constant", function(compressor){
|
||||
var unaryPrefix = makePredicate("! ~ - +");
|
||||
AST_Node.DEFMETHOD("is_constant", function(){
|
||||
// Accomodate when compress option evaluate=false
|
||||
// as well as the common constant expressions !0 and !1
|
||||
return this instanceof AST_Constant
|
||||
|| (this instanceof AST_UnaryPrefix && this.operator == "!"
|
||||
&& this.expression instanceof AST_Constant)
|
||||
|| this.evaluate(compressor).length > 1;
|
||||
// as well as the common constant expressions !0 and -1
|
||||
if (this instanceof AST_Constant) {
|
||||
return !(this instanceof AST_RegExp);
|
||||
} else {
|
||||
return this instanceof AST_UnaryPrefix
|
||||
&& this.expression instanceof AST_Constant
|
||||
&& unaryPrefix(this.operator);
|
||||
}
|
||||
});
|
||||
// Obtain the constant value of an expression already known to be constant.
|
||||
// Result only valid iff this.is_constant(compressor) is true.
|
||||
// Result only valid iff this.is_constant() is true.
|
||||
AST_Node.DEFMETHOD("constant_value", function(compressor){
|
||||
// Accomodate when option evaluate=false.
|
||||
if (this instanceof AST_Constant) return this.value;
|
||||
// Accomodate the common constant expressions !0 and !1 when option evaluate=false.
|
||||
if (this instanceof AST_UnaryPrefix
|
||||
&& this.operator == "!"
|
||||
&& this.expression instanceof AST_Constant) {
|
||||
return !this.expression.value;
|
||||
if (this instanceof AST_Constant && !(this instanceof AST_RegExp)) {
|
||||
return this.value;
|
||||
}
|
||||
var result = this.evaluate(compressor)
|
||||
// Accomodate the common constant expressions !0 and -1 when option evaluate=false.
|
||||
if (this instanceof AST_UnaryPrefix
|
||||
&& this.expression instanceof AST_Constant) switch (this.operator) {
|
||||
case "!":
|
||||
return !this.expression.value;
|
||||
case "~":
|
||||
return ~this.expression.value;
|
||||
case "-":
|
||||
return -this.expression.value;
|
||||
case "+":
|
||||
return +this.expression.value;
|
||||
default:
|
||||
throw new Error(string_template("Cannot evaluate unary expression {value}", {
|
||||
value: this.print_to_string()
|
||||
}));
|
||||
}
|
||||
var result = this.evaluate(compressor);
|
||||
if (result.length > 1) {
|
||||
return result[1];
|
||||
}
|
||||
// should never be reached
|
||||
return undefined;
|
||||
throw new Error(string_template("Cannot evaluate constant [{file}:{line},{col}]", this.start));
|
||||
});
|
||||
def(AST_Statement, function(){
|
||||
throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start));
|
||||
@@ -1071,6 +1104,32 @@ merge(Compressor.prototype, {
|
||||
if (this.segments.length !== 1) throw def;
|
||||
return this.segments[0].value;
|
||||
});
|
||||
def(AST_Array, function(compressor){
|
||||
if (compressor.option("unsafe")) {
|
||||
return this.elements.map(function(element) {
|
||||
return ev(element, compressor);
|
||||
});
|
||||
}
|
||||
throw def;
|
||||
});
|
||||
def(AST_Object, function(compressor){
|
||||
if (compressor.option("unsafe")) {
|
||||
var val = {};
|
||||
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) {
|
||||
key = ev(key, compressor);
|
||||
}
|
||||
if (typeof Object.prototype[key] === 'function') {
|
||||
throw def;
|
||||
}
|
||||
val[key] = ev(prop.value, compressor);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
throw def;
|
||||
});
|
||||
def(AST_UnaryPrefix, function(compressor){
|
||||
var e = this.expression;
|
||||
switch (this.operator) {
|
||||
@@ -1140,6 +1199,12 @@ merge(Compressor.prototype, {
|
||||
try {
|
||||
var d = this.definition();
|
||||
if (d && (d.constant || compressor.option("reduce_vars") && !d.modified) && d.init) {
|
||||
if (compressor.option("unsafe")) {
|
||||
if (!HOP(d.init, '_evaluated')) {
|
||||
d.init._evaluated = ev(d.init, compressor);
|
||||
}
|
||||
return d.init._evaluated;
|
||||
}
|
||||
return ev(d.init, compressor);
|
||||
}
|
||||
} finally {
|
||||
@@ -1147,11 +1212,16 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
throw def;
|
||||
});
|
||||
def(AST_Dot, function(compressor){
|
||||
if (compressor.option("unsafe") && this.property == "length") {
|
||||
var str = ev(this.expression, compressor);
|
||||
if (typeof str == "string")
|
||||
return str.length;
|
||||
def(AST_PropAccess, function(compressor){
|
||||
if (compressor.option("unsafe")) {
|
||||
var key = this.property;
|
||||
if (key instanceof AST_Node) {
|
||||
key = ev(key, compressor);
|
||||
}
|
||||
var val = ev(this.expression, compressor);
|
||||
if (val && HOP(val, key)) {
|
||||
return val[key];
|
||||
}
|
||||
}
|
||||
throw def;
|
||||
});
|
||||
@@ -2494,6 +2564,16 @@ merge(Compressor.prototype, {
|
||||
var commutativeOperators = makePredicate("== === != !== * & | ^");
|
||||
|
||||
OPT(AST_Binary, function(self, compressor){
|
||||
var lhs = self.left.evaluate(compressor);
|
||||
var rhs = self.right.evaluate(compressor);
|
||||
if (lhs.length > 1 && lhs[0].is_constant() !== self.left.is_constant()
|
||||
|| rhs.length > 1 && rhs[0].is_constant() !== self.right.is_constant()) {
|
||||
return make_node(AST_Binary, self, {
|
||||
operator: self.operator,
|
||||
left: lhs[0],
|
||||
right: rhs[0]
|
||||
}).optimize(compressor);
|
||||
}
|
||||
function reverse(op, force) {
|
||||
if (force || !(self.left.has_side_effects(compressor) || self.right.has_side_effects(compressor))) {
|
||||
if (op) self.operator = op;
|
||||
@@ -2566,32 +2646,6 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (compressor.option("conditionals")) {
|
||||
if (self.operator == "&&") {
|
||||
var ll = self.left.evaluate(compressor);
|
||||
if (ll.length > 1) {
|
||||
if (ll[1]) {
|
||||
compressor.warn("Condition left of && always true [{file}:{line},{col}]", self.start);
|
||||
return maintain_this_binding(compressor.parent(), self, self.right.evaluate(compressor)[0]);
|
||||
} else {
|
||||
compressor.warn("Condition left of && always false [{file}:{line},{col}]", self.start);
|
||||
return maintain_this_binding(compressor.parent(), self, ll[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (self.operator == "||") {
|
||||
var ll = self.left.evaluate(compressor);
|
||||
if (ll.length > 1) {
|
||||
if (ll[1]) {
|
||||
compressor.warn("Condition left of || always true [{file}:{line},{col}]", self.start);
|
||||
return maintain_this_binding(compressor.parent(), self, ll[0]);
|
||||
} else {
|
||||
compressor.warn("Condition left of || always false [{file}:{line},{col}]", self.start);
|
||||
return maintain_this_binding(compressor.parent(), self, self.right.evaluate(compressor)[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (compressor.option("booleans") && compressor.in_boolean_context()) switch (self.operator) {
|
||||
case "&&":
|
||||
var ll = self.left.evaluate(compressor);
|
||||
@@ -2665,6 +2719,30 @@ merge(Compressor.prototype, {
|
||||
return self.left;
|
||||
}
|
||||
if (compressor.option("evaluate")) {
|
||||
switch (self.operator) {
|
||||
case "&&":
|
||||
if (self.left.is_constant()) {
|
||||
if (self.left.constant_value(compressor)) {
|
||||
compressor.warn("Condition left of && always true [{file}:{line},{col}]", self.start);
|
||||
return maintain_this_binding(compressor.parent(), self, self.right);
|
||||
} else {
|
||||
compressor.warn("Condition left of && always false [{file}:{line},{col}]", self.start);
|
||||
return maintain_this_binding(compressor.parent(), self, self.left);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "||":
|
||||
if (self.left.is_constant()) {
|
||||
if (self.left.constant_value(compressor)) {
|
||||
compressor.warn("Condition left of || always true [{file}:{line},{col}]", self.start);
|
||||
return maintain_this_binding(compressor.parent(), self, self.left);
|
||||
} else {
|
||||
compressor.warn("Condition left of || always false [{file}:{line},{col}]", self.start);
|
||||
return maintain_this_binding(compressor.parent(), self, self.right);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (self.operator == "+") {
|
||||
if (self.left instanceof AST_Constant
|
||||
&& self.right instanceof AST_Binary
|
||||
@@ -2738,14 +2816,6 @@ merge(Compressor.prototype, {
|
||||
});
|
||||
|
||||
OPT(AST_SymbolRef, function(self, compressor){
|
||||
function isLHS(symbol, parent) {
|
||||
return (
|
||||
parent instanceof AST_Binary &&
|
||||
parent.operator === '=' &&
|
||||
parent.left === symbol
|
||||
);
|
||||
}
|
||||
|
||||
if (self.undeclared() && !isLHS(self, compressor.parent())) {
|
||||
var defines = compressor.option("global_defs");
|
||||
if (defines && HOP(defines, self.name)) {
|
||||
@@ -2899,14 +2969,14 @@ merge(Compressor.prototype, {
|
||||
});
|
||||
}
|
||||
// y?1:1 --> 1
|
||||
if (consequent.is_constant(compressor)
|
||||
&& alternative.is_constant(compressor)
|
||||
if (consequent.is_constant()
|
||||
&& alternative.is_constant()
|
||||
&& consequent.equivalent_to(alternative)) {
|
||||
var consequent_value = consequent.constant_value(compressor);
|
||||
var consequent_value = consequent.evaluate(compressor)[0];
|
||||
if (self.condition.has_side_effects(compressor)) {
|
||||
return AST_Seq.from_array([self.condition, make_node_from_constant(compressor, consequent_value, self)]);
|
||||
return AST_Seq.from_array([self.condition, consequent_value]);
|
||||
} else {
|
||||
return make_node_from_constant(compressor, consequent_value, self);
|
||||
return consequent_value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3023,7 +3093,7 @@ merge(Compressor.prototype, {
|
||||
});
|
||||
}
|
||||
}
|
||||
return self;
|
||||
return self.evaluate(compressor)[0];
|
||||
});
|
||||
|
||||
OPT(AST_Dot, function(self, compressor){
|
||||
|
||||
Reference in New Issue
Block a user