Merge branch 'master' into harmony-v3.0.24

This commit is contained in:
alexlamsl
2017-07-08 13:12:54 +08:00
15 changed files with 706 additions and 228 deletions

View File

@@ -82,6 +82,7 @@ function Compressor(options, false_by_default) {
switches : !false_by_default,
top_retain : null,
toplevel : !!(options && options["top_retain"]),
typeofs : !false_by_default,
unsafe : false,
unsafe_comps : false,
unsafe_Func : false,
@@ -850,16 +851,20 @@ merge(Compressor.prototype, {
function has_overlapping_symbol(fn, arg) {
var found = false;
arg.walk(new TreeWalker(function(node) {
var tw = new TreeWalker(function(node) {
if (found) return true;
if (node instanceof AST_SymbolRef && fn.variables.has(node.name)) {
var s = node.definition().scope;
if (s !== scope) while (s = s.parent_scope) {
if (s === scope) return true;
}
found = true;
return found = true;
}
}));
if (node instanceof AST_This && !tw.find_parent(AST_Scope)) {
return found = true;
}
});
arg.walk(tw);
return found;
}
@@ -874,13 +879,17 @@ merge(Compressor.prototype, {
&& all(iife.args, function(arg) {
return !(arg instanceof AST_Expansion);
})) {
fn.argnames.forEach(function(sym, i) {
var names = Object.create(null);
for (var i = fn.argnames.length; --i >= 0;) {
var sym = fn.argnames[i];
if (sym.name in names) continue;
names[sym.name] = true;
if (sym instanceof AST_Expansion) {
var elements = iife.args.slice(i);
if (all(elements, function(arg) {
return !has_overlapping_symbol(fn, arg);
})) {
candidates.push(make_node(AST_VarDef, sym, {
candidates.unshift(make_node(AST_VarDef, sym, {
name: sym.expression,
value: make_node(AST_Array, iife, {
elements: elements
@@ -891,12 +900,12 @@ merge(Compressor.prototype, {
var arg = iife.args[i];
if (!arg) arg = make_node(AST_Undefined, sym);
else if (has_overlapping_symbol(fn, arg)) arg = null;
if (arg) candidates.push(make_node(AST_VarDef, sym, {
if (arg) candidates.unshift(make_node(AST_VarDef, sym, {
name: sym,
value: arg
}));
}
});
}
}
}
@@ -1268,21 +1277,21 @@ merge(Compressor.prototype, {
for (var i = 0, len = statements.length; i < len; i++) {
var stat = statements[i];
if (prev) {
if (stat instanceof AST_For) {
try {
prev.body.walk(new TreeWalker(function(node){
if (node instanceof AST_Binary && node.operator == "in")
throw cons_seq;
}));
if (stat.init && !(stat.init instanceof AST_Definitions)) {
stat.init = cons_seq(stat.init);
if (stat instanceof AST_For && !(stat.init instanceof AST_Definitions)) {
var abort = false;
prev.body.walk(new TreeWalker(function(node) {
if (abort || node instanceof AST_Scope) return true;
if (node instanceof AST_Binary && node.operator == "in") {
abort = true;
return true;
}
else if (!stat.init) {
}));
if (!abort) {
if (stat.init) stat.init = cons_seq(stat.init);
else {
stat.init = prev.body.drop_side_effect_free(compressor);
n--;
}
} catch(ex) {
if (ex !== cons_seq) throw ex;
}
}
else if (stat instanceof AST_If) {
@@ -1613,13 +1622,8 @@ merge(Compressor.prototype, {
// descendant of AST_Node.
AST_Node.DEFMETHOD("evaluate", function(compressor){
if (!compressor.option("evaluate")) return this;
try {
var val = this._eval(compressor);
return !val || val instanceof RegExp || typeof val != "object" ? val : this;
} catch(ex) {
if (ex !== def) throw ex;
return this;
}
var val = this._eval(compressor);
return !val || val instanceof RegExp || typeof val != "object" ? val : this;
});
var unaryPrefix = makePredicate("! ~ - + void");
AST_Node.DEFMETHOD("is_constant", function(){
@@ -1665,34 +1669,33 @@ merge(Compressor.prototype, {
def(AST_Statement, function(){
throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start));
});
def(AST_Lambda, function(){
throw def;
});
def(AST_Class, function() {
throw def;
});
def(AST_Lambda, return_this);
def(AST_Class, return_this);
function ev(node, compressor) {
if (!compressor) throw new Error("Compressor must be passed");
return node._eval(compressor);
};
def(AST_Node, function(){
throw def; // not constant
});
def(AST_Node, return_this);
def(AST_Constant, function(){
return this.getValue();
});
def(AST_TemplateString, function() {
if (this.segments.length !== 1) throw def;
if (this.segments.length !== 1) return this;
return this.segments[0].value;
});
def(AST_Array, function(compressor){
if (compressor.option("unsafe")) {
return this.elements.map(function(element) {
return ev(element, compressor);
});
var elements = [];
for (var i = 0, len = this.elements.length; i < len; i++) {
var element = this.elements[i];
var value = ev(element, compressor);
if (element === value) return this;
elements.push(value);
}
return elements;
}
throw def;
return this;
});
def(AST_Object, function(compressor){
if (compressor.option("unsafe")) {
@@ -1704,164 +1707,263 @@ merge(Compressor.prototype, {
key = key.name;
} else if (key instanceof AST_Node) {
key = ev(key, compressor);
if (key === prop.key) return this;
}
if (typeof Object.prototype[key] === 'function') {
throw def;
return this;
}
val[key] = ev(prop.value, compressor);
if (val[key] === prop.value) return this;
}
return val;
}
throw def;
return this;
});
def(AST_UnaryPrefix, function(compressor){
var e = this.expression;
// Function would be evaluated to an array and so typeof would
// incorrectly return 'object'. Hence making is a special case.
if (this.operator == "typeof" && is_func_expr(this.expression)) {
return typeof function(){};
}
var e = ev(this.expression, compressor);
if (e === this.expression) return this;
switch (this.operator) {
case "!": return !ev(e, compressor);
case "!": return !e;
case "typeof":
// Function would be evaluated to an array and so typeof would
// incorrectly return 'object'. Hence making is a special case.
if (is_func_expr(e)) return typeof function(){};
e = ev(e, compressor);
// typeof <RegExp> returns "object" or "function" on different platforms
// so cannot evaluate reliably
if (e instanceof RegExp) throw def;
if (e instanceof RegExp) return this;
return typeof e;
case "void": return void ev(e, compressor);
case "~": return ~ev(e, compressor);
case "-": return -ev(e, compressor);
case "+": return +ev(e, compressor);
case "void": return void e;
case "~": return ~e;
case "-": return -e;
case "+": return +e;
}
throw def;
return this;
});
def(AST_Binary, function(c){
var left = this.left, right = this.right, result;
def(AST_Binary, function(compressor){
var left = ev(this.left, compressor);
if (left === this.left) return this;
var right = ev(this.right, compressor);
if (right === this.right) return this;
var result;
switch (this.operator) {
case "&&" : result = ev(left, c) && ev(right, c); break;
case "||" : result = ev(left, c) || ev(right, c); break;
case "|" : result = ev(left, c) | ev(right, c); break;
case "&" : result = ev(left, c) & ev(right, c); break;
case "^" : result = ev(left, c) ^ ev(right, c); break;
case "+" : result = ev(left, c) + ev(right, c); break;
case "*" : result = ev(left, c) * ev(right, c); break;
case "**" : result = Math.pow(ev(left, c), ev(right, c)); break;
case "/" : result = ev(left, c) / ev(right, c); break;
case "%" : result = ev(left, c) % ev(right, c); break;
case "-" : result = ev(left, c) - ev(right, c); break;
case "<<" : result = ev(left, c) << ev(right, c); break;
case ">>" : result = ev(left, c) >> ev(right, c); break;
case ">>>" : result = ev(left, c) >>> ev(right, c); break;
case "==" : result = ev(left, c) == ev(right, c); break;
case "===" : result = ev(left, c) === ev(right, c); break;
case "!=" : result = ev(left, c) != ev(right, c); break;
case "!==" : result = ev(left, c) !== ev(right, c); break;
case "<" : result = ev(left, c) < ev(right, c); break;
case "<=" : result = ev(left, c) <= ev(right, c); break;
case ">" : result = ev(left, c) > ev(right, c); break;
case ">=" : result = ev(left, c) >= ev(right, c); break;
case "&&" : result = left && right; break;
case "||" : result = left || right; break;
case "|" : result = left | right; break;
case "&" : result = left & right; break;
case "^" : result = left ^ right; break;
case "+" : result = left + right; break;
case "*" : result = left * right; break;
case "**" : result = Math.pow(left, right); break;
case "/" : result = left / right; break;
case "%" : result = left % right; break;
case "-" : result = left - right; break;
case "<<" : result = left << right; break;
case ">>" : result = left >> right; break;
case ">>>" : result = left >>> right; break;
case "==" : result = left == right; break;
case "===" : result = left === right; break;
case "!=" : result = left != right; break;
case "!==" : result = left !== right; break;
case "<" : result = left < right; break;
case "<=" : result = left <= right; break;
case ">" : result = left > right; break;
case ">=" : result = left >= right; break;
default:
throw def;
return this;
}
if (isNaN(result) && c.find_parent(AST_With)) {
if (isNaN(result) && compressor.find_parent(AST_With)) {
// leave original expression as is
throw def;
return this;
}
return result;
});
def(AST_Conditional, function(compressor){
return ev(this.condition, compressor)
? ev(this.consequent, compressor)
: ev(this.alternative, compressor);
var condition = ev(this.condition, compressor);
if (condition === this.condition) return this;
var node = condition ? this.consequent : this.alternative;
var value = ev(node, compressor);
return value === node ? this : value;
});
def(AST_SymbolRef, function(compressor){
if (!compressor.option("reduce_vars") || this._evaluating) throw def;
this._evaluating = true;
try {
var fixed = this.fixed_value();
if (!fixed) throw def;
var value = ev(fixed, compressor);
if (!HOP(fixed, "_eval")) fixed._eval = function() {
return value;
};
if (value && typeof value == "object" && this.definition().escaped) throw def;
return value;
} finally {
this._evaluating = false;
if (!compressor.option("reduce_vars")) return this;
var fixed = this.fixed_value();
if (!fixed) return this;
this._eval = return_this;
var value = ev(fixed, compressor);
if (value === fixed) {
delete this._eval;
return this;
}
if (!HOP(fixed, "_eval")) fixed._eval = function() {
return value;
};
if (value && typeof value == "object" && this.definition().escaped) {
delete this._eval;
return this;
}
this._eval = fixed._eval;
return value;
});
var global_objs = {
Array: Array,
Boolean: Boolean,
Math: Math,
Number: Number,
RegExp: RegExp,
Object: Object,
String: String,
};
function convert_to_predicate(obj) {
for (var key in obj) {
obj[key] = makePredicate(obj[key]);
}
}
var static_values = {
Math: [
"E",
"LN10",
"LN2",
"LOG2E",
"LOG10E",
"PI",
"SQRT1_2",
"SQRT2",
],
Number: [
"MAX_VALUE",
"MIN_VALUE",
"NaN",
"NEGATIVE_INFINITY",
"POSITIVE_INFINITY",
],
};
convert_to_predicate(static_values);
def(AST_PropAccess, function(compressor){
if (compressor.option("unsafe")) {
var key = this.property;
if (key instanceof AST_Node) {
key = ev(key, compressor);
if (key === this.property) return this;
}
var val = ev(this.expression, compressor);
if (val && HOP(val, key)) {
return val[key];
var exp = this.expression;
var val;
if (exp instanceof AST_SymbolRef && exp.undeclared()) {
if (!(static_values[exp.name] || return_false)(key)) return this;
val = global_objs[exp.name];
} else {
val = ev(exp, compressor);
if (!val || val === exp || !HOP(val, key)) return this;
}
return val[key];
}
throw def;
return this;
});
var object_fns = [
'constructor',
'toString',
'valueOf',
"constructor",
"toString",
"valueOf",
];
var native_fns = {
Array: makePredicate([
'indexOf',
'join',
'lastIndexOf',
'slice',
].concat(object_fns)),
Boolean: makePredicate(object_fns),
Number: makePredicate([
'toExponential',
'toFixed',
'toPrecision',
].concat(object_fns)),
RegExp: makePredicate([
'test',
].concat(object_fns)),
String: makePredicate([
'charAt',
'charCodeAt',
'concat',
'indexOf',
'italics',
'lastIndexOf',
'match',
'replace',
'search',
'slice',
'split',
'substr',
'substring',
'trim',
].concat(object_fns)),
Array: [
"indexOf",
"join",
"lastIndexOf",
"slice",
].concat(object_fns),
Boolean: object_fns,
Number: [
"toExponential",
"toFixed",
"toPrecision",
].concat(object_fns),
RegExp: [
"test",
].concat(object_fns),
String: [
"charAt",
"charCodeAt",
"concat",
"indexOf",
"italics",
"lastIndexOf",
"match",
"replace",
"search",
"slice",
"split",
"substr",
"substring",
"trim",
].concat(object_fns),
};
convert_to_predicate(native_fns);
var static_fns = {
Array: [
"isArray",
],
Math: [
"abs",
"acos",
"asin",
"atan",
"ceil",
"cos",
"exp",
"floor",
"log",
"round",
"sin",
"sqrt",
"tan",
"atan2",
"pow",
"max",
"min"
],
Number: [
"isFinite",
"isNaN",
],
Object: [
"keys",
"getOwnPropertyNames",
],
String: [
"fromCharCode",
],
};
convert_to_predicate(static_fns);
def(AST_Call, function(compressor){
var exp = this.expression;
if (compressor.option("unsafe") && exp instanceof AST_PropAccess) {
var key = exp.property;
if (key instanceof AST_Node) {
key = ev(key, compressor);
if (key === exp.property) return this;
}
var val = ev(exp.expression, compressor);
if ((val && native_fns[val.constructor.name] || return_false)(key)) {
return val[key].apply(val, this.args.map(function(arg) {
return ev(arg, compressor);
}));
var val;
var e = exp.expression;
if (e instanceof AST_SymbolRef && e.undeclared()) {
if (!(static_fns[e.name] || return_false)(key)) return this;
val = global_objs[e.name];
} else {
val = ev(e, compressor);
if (val === e || !(val && native_fns[val.constructor.name] || return_false)(key)) return this;
}
var args = [];
for (var i = 0, len = this.args.length; i < len; i++) {
var arg = this.args[i];
var value = ev(arg, compressor);
if (arg === value) return this;
args.push(value);
}
return val[key].apply(val, args);
}
throw def;
});
def(AST_New, function(compressor){
throw def;
return this;
});
def(AST_New, return_this);
})(function(node, func){
node.DEFMETHOD("_eval", func);
});
@@ -3782,7 +3884,8 @@ merge(Compressor.prototype, {
case "==":
case "!=":
// "undefined" == typeof x => undefined === x
if (self.left instanceof AST_String
if (compressor.option("typeofs")
&& self.left instanceof AST_String
&& self.left.value == "undefined"
&& self.right instanceof AST_UnaryPrefix
&& self.right.operator == "typeof") {
@@ -4510,6 +4613,17 @@ merge(Compressor.prototype, {
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 && !(node instanceof AST_Arrow)) return true;
}));
return result;
});
OPT(AST_Dot, function(self, compressor){
var def = self.resolve_defines(compressor);
if (def) {
@@ -4524,6 +4638,20 @@ merge(Compressor.prototype, {
})
}).optimize(compressor);
}
if (compressor.option("unsafe") && self.expression instanceof AST_Object) {
var values = self.expression.properties;
for (var i = values.length; --i >= 0;) {
if (values[i].key === prop) {
var value = values[i].value;
if (value instanceof AST_Function ? !value.contains_this() : !value.has_side_effects(compressor)) {
var obj = self.expression.clone();
obj.properties = obj.properties.slice();
obj.properties.splice(i, 1);
return make_sequence(self, [ obj, value ]).optimize(compressor);
}
}
}
}
if (compressor.option("unsafe_proto")
&& self.expression instanceof AST_Dot
&& self.expression.property == "prototype") {
@@ -4656,17 +4784,6 @@ merge(Compressor.prototype, {
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 && !(node instanceof AST_Arrow)) return true;
}));
return result;
});
OPT(AST_ConciseMethod, function(self, compressor){
// p(){return x;} ---> p:()=>x
if (compressor.option("arrows")