Compare commits

...

22 Commits

Author SHA1 Message Date
Alex Lam S.L
30761eede5 v3.10.4 2020-09-07 00:25:54 +08:00
Alex Lam S.L
fb30aeccaf relax ufuzz job timing constraint (#4094) 2020-09-05 19:29:50 +08:00
Alex Lam S.L
226aa1f76b enhance unsafe_math (#4093) 2020-09-04 10:14:39 +08:00
Alex Lam S.L
6e235602fb fix corner case in loops & unused (#4092)
fixes #4091
2020-09-04 01:51:26 +08:00
Alex Lam S.L
980fcbb56b enhance unused (#4090) 2020-09-03 17:41:33 +08:00
Alex Lam S.L
375ebe316d enhance join_vars (#4089) 2020-09-03 01:41:10 +08:00
Alex Lam S.L
2500930234 enhance reduce_vars (#4088) 2020-09-02 11:30:46 +08:00
Alex Lam S.L
2f0da2ff05 reduce AST_ForIn gracefully (#4087) 2020-09-02 08:51:43 +08:00
Alex Lam S.L
83a3cbf151 fix test case runtime accounting (#4086) 2020-09-02 03:23:08 +08:00
Alex Lam S.L
da8d154571 fix corner case in loops & unused (#4085)
fixes #4084
2020-09-02 03:20:58 +08:00
Alex Lam S.L
e33c727e8b v3.10.3 2020-08-30 13:09:12 +01:00
Alex Lam S.L
f886b3fb2b fix corner case in loops & unused (#4083)
fixes #4082
2020-08-29 02:42:17 +08:00
Alex Lam S.L
b1cc15e85b fix corner case in sequences (#4080)
fixes #4079
2020-08-26 20:41:11 +08:00
Alex Lam S.L
3aa765e429 fix corner case in evaluate (#4078)
fixes #4077
2020-08-26 19:45:38 +08:00
Alex Lam S.L
93d084a1d1 fix corner case in loops & unused (#4076)
fixes #4075
2020-08-26 17:32:20 +08:00
Alex Lam S.L
c7a3e09407 enhance loops & unused (#4074)
- extend `ufuzz` generation of for-in loops
2020-08-26 09:32:55 +08:00
Alex Lam S.L
09525c7530 fix corner case in sequences (#4073) 2020-08-26 01:26:49 +08:00
Alex Lam S.L
a7e15fe73c streamline parenthesis logic (#4072) 2020-08-25 19:45:37 +08:00
Alex Lam S.L
a31c27c7cf fix corner case in collapse_vars (#4071)
fixes #4070
2020-08-25 17:23:36 +08:00
Alex Lam S.L
1caf7c7bd2 minor clean up (#4069) 2020-08-25 10:10:56 +08:00
Alex Lam S.L
0eb0c9b388 fix corner case in evaluate (#4068)
fixes #4067
2020-08-24 14:57:26 +08:00
Alex Lam S.L
7dc61cdc89 tidy up various interfaces (#4066) 2020-08-24 04:39:38 +08:00
21 changed files with 840 additions and 457 deletions

View File

@@ -288,10 +288,13 @@ var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", {
var label = node.label;
var def = this.label;
node.walk(new TreeWalker(function(node) {
if (node instanceof AST_LoopControl && node.label && node.label.thedef === def) {
if (node instanceof AST_LoopControl) {
if (!node.label || node.label.thedef !== def) return;
node.label.thedef = label;
label.references.push(node);
return true;
}
if (node instanceof AST_Scope) return true;
}));
}
return node;
@@ -409,16 +412,16 @@ var AST_With = DEFNODE("With", "expression", {
/* -----[ scope and functions ]----- */
var AST_Scope = DEFNODE("Scope", "variables functions uses_with uses_eval parent_scope enclosed cname", {
var AST_Scope = DEFNODE("Scope", "cname enclosed uses_eval uses_with parent_scope functions variables make_def", {
$documentation: "Base class for all statements introducing a lexical scope",
$propdoc: {
variables: "[Object/S] a map of name -> SymbolDef for all variables/functions defined in this scope",
functions: "[Object/S] like `variables`, but only lists function declarations",
uses_with: "[boolean/S] tells whether this scope uses the `with` statement",
uses_eval: "[boolean/S] tells whether this scope contains a direct call to the global `eval`",
parent_scope: "[AST_Scope?/S] link to the parent scope",
enclosed: "[SymbolDef*/S] a list of all symbol definitions that are accessed from this scope or any subscopes",
cname: "[integer/S] current index for mangling variables (used internally by the mangler)",
enclosed: "[SymbolDef*/S] a list of all symbol definitions that are accessed from this scope or any subscopes",
uses_eval: "[boolean/S] tells whether this scope contains a direct call to the global `eval`",
uses_with: "[boolean/S] tells whether this scope uses the `with` statement",
parent_scope: "[AST_Scope?/S] link to the parent scope",
functions: "[Object/S] like `variables`, but only lists function declarations",
variables: "[Object/S] a map of name -> SymbolDef for all variables/functions defined in this scope",
},
clone: function(deep) {
var node = this._clone(deep);

View File

@@ -325,22 +325,22 @@ merge(Compressor.prototype, {
}
var lhs = is_lhs(node, parent);
if (lhs) return lhs;
if (!immutable
&& parent instanceof AST_Call
&& parent.expression === node
&& !parent.is_expr_pure(compressor)
&& (!(value instanceof AST_Function)
|| !(parent instanceof AST_New) && value.contains_this())) {
return true;
if (parent instanceof AST_Array) return is_modified(compressor, tw, parent, parent, level + 1);
if (parent instanceof AST_Call) {
return !immutable
&& parent.expression === node
&& !parent.is_expr_pure(compressor)
&& (!(value instanceof AST_Function)
|| !(parent instanceof AST_New) && value.contains_this());
}
if (parent instanceof AST_Array) {
return is_modified(compressor, tw, parent, parent, level + 1);
}
if (parent instanceof AST_ObjectKeyVal && node === parent.value) {
if (parent instanceof AST_ForIn) return parent.init === node;
if (parent instanceof AST_ObjectKeyVal) {
if (parent.value !== node) return;
var obj = tw.parent(level + 1);
return is_modified(compressor, tw, obj, obj, level + 2);
}
if (parent instanceof AST_PropAccess && parent.expression === node) {
if (parent instanceof AST_PropAccess) {
if (parent.expression !== node) return;
var prop = read_property(value, parent);
return (!immutable || recursive) && is_modified(compressor, tw, parent, prop, level + 1);
}
@@ -514,33 +514,41 @@ merge(Compressor.prototype, {
|| value instanceof AST_This;
}
function has_escaped(d, node, parent) {
if (parent instanceof AST_Assign) return parent.operator == "=" && parent.right === node;
if (parent instanceof AST_Call) return parent.expression !== node || parent instanceof AST_New;
if (parent instanceof AST_Exit) return parent.value === node && node.scope !== d.scope;
if (parent instanceof AST_VarDef) return parent.value === node;
}
function value_in_use(node, parent) {
if (parent instanceof AST_Array) return true;
if (parent instanceof AST_Binary) return lazy_op[parent.operator];
if (parent instanceof AST_Conditional) return parent.condition !== node;
if (parent instanceof AST_Sequence) return parent.tail_node() === node;
}
function mark_escaped(tw, d, scope, node, value, level, depth) {
var parent = tw.parent(level);
if (value && value.is_constant()) return;
if (parent instanceof AST_Assign && parent.operator == "=" && node === parent.right
|| parent instanceof AST_Call && (node !== parent.expression || parent instanceof AST_New)
|| parent instanceof AST_Exit && node === parent.value && node.scope !== d.scope
|| parent instanceof AST_VarDef && node === parent.value) {
if (has_escaped(d, node, parent)) {
d.escaped.push(parent);
if (depth > 1 && !(value && value.is_constant_expression(scope))) depth = 1;
if (!d.escaped.depth || d.escaped.depth > depth) d.escaped.depth = depth;
return;
} else if (parent instanceof AST_Array
|| parent instanceof AST_Binary && lazy_op[parent.operator]
|| parent instanceof AST_Conditional && node !== parent.condition
|| parent instanceof AST_Sequence && node === parent.tail_node()) {
} else if (value_in_use(node, parent)) {
mark_escaped(tw, d, scope, parent, parent, level + 1, depth);
} else if (parent instanceof AST_ObjectKeyVal && node === parent.value) {
} else if (parent instanceof AST_ObjectKeyVal && parent.value === node) {
var obj = tw.parent(level + 1);
mark_escaped(tw, d, scope, obj, obj, level + 2, depth);
} else if (parent instanceof AST_PropAccess && node === parent.expression) {
} else if (parent instanceof AST_PropAccess && parent.expression === node) {
value = read_property(value, parent);
mark_escaped(tw, d, scope, parent, value, level + 1, depth + 1);
if (value) return;
}
if (level > 0) return;
if (parent instanceof AST_Call && node === parent.expression) return;
if (parent instanceof AST_Sequence && node !== parent.tail_node()) return;
if (parent instanceof AST_Call && parent.expression === node) return;
if (parent instanceof AST_Sequence && parent.tail_node() !== node) return;
if (parent instanceof AST_SimpleStatement) return;
if (parent instanceof AST_Unary && !unary_side_effects[parent.operator]) return;
d.direct_access = true;
@@ -554,13 +562,6 @@ merge(Compressor.prototype, {
if (is_arguments(def) && node.property instanceof AST_Number) def.reassigned = true;
}
var suppressor = new TreeWalker(function(node) {
if (!(node instanceof AST_Symbol)) return;
var d = node.definition();
if (!d) return;
if (node instanceof AST_SymbolRef) push_ref(d, node);
d.fixed = false;
});
def(AST_Accessor, function(tw, descend, compressor) {
push(tw);
reset_variables(tw, compressor, this);
@@ -740,11 +741,17 @@ merge(Compressor.prototype, {
return true;
});
def(AST_ForIn, function(tw) {
this.init.walk(suppressor);
this.object.walk(tw);
var saved_loop = tw.in_loop;
tw.in_loop = this;
push(tw);
var init = this.init;
init.walk(tw);
if (init instanceof AST_SymbolRef) {
init.definition().fixed = false;
} else if (init instanceof AST_Var) {
init.definitions[0].name.definition().fixed = false;
}
this.body.walk(tw);
pop(tw);
tw.in_loop = saved_loop;
@@ -1093,11 +1100,14 @@ merge(Compressor.prototype, {
// func(something) because that changes the meaning of
// the func (becomes lexical instead of global).
function maintain_this_binding(compressor, parent, orig, val) {
if (parent instanceof AST_UnaryPrefix && parent.operator == "delete"
|| parent.TYPE == "Call" && parent.expression === orig && needs_unbinding(compressor, val)) {
return make_sequence(orig, [ make_node(AST_Number, orig, { value: 0 }), val ]);
var wrap = false;
if (parent.TYPE == "Call") {
wrap = parent.expression === orig && needs_unbinding(compressor, val);
} else if (parent instanceof AST_UnaryPrefix) {
wrap = parent.operator == "delete"
|| parent.operator == "typeof" && is_undeclared_ref(val);
}
return val;
return wrap ? make_sequence(orig, [ make_node(AST_Number, orig, { value: 0 }), val ]) : val;
}
function merge_sequence(array, node) {
@@ -1286,7 +1296,7 @@ merge(Compressor.prototype, {
col: node.start.col
});
if (candidate instanceof AST_UnaryPostfix) {
lhs.definition().fixed = false;
if (lhs instanceof AST_SymbolRef) lhs.definition().fixed = false;
return make_node(AST_UnaryPrefix, candidate, candidate);
}
if (candidate instanceof AST_VarDef) {
@@ -2634,6 +2644,14 @@ merge(Compressor.prototype, {
defs = stat.init;
}
} else if (stat instanceof AST_ForIn) {
if (defs && defs.TYPE == stat.init.TYPE) {
defs.definitions = defs.definitions.concat(stat.init.definitions);
var name = stat.init.definitions[0].name;
var ref = make_node(AST_SymbolRef, name, name);
name.definition().references.push(ref);
stat.init = ref;
CHANGED = true;
}
stat.object = join_assigns_expr(stat.object);
} else if (stat instanceof AST_If) {
stat.condition = join_assigns_expr(stat.condition);
@@ -3152,11 +3170,11 @@ merge(Compressor.prototype, {
return this.expression._find_defs(compressor, "." + this.property + suffix);
});
def(AST_SymbolDeclaration, function(compressor) {
if (!this.global()) return;
if (!this.definition().global) return;
if (HOP(compressor.option("global_defs"), this.name)) warn(this);
});
def(AST_SymbolRef, function(compressor, suffix) {
if (!this.global()) return;
if (!this.definition().global) return;
var defines = compressor.option("global_defs");
var name = this.name + suffix;
if (HOP(defines, name)) return to_node(defines[name], this);
@@ -3501,7 +3519,8 @@ merge(Compressor.prototype, {
var left = this.left._eval(compressor, ignore_side_effects, cached, depth);
if (left === this.left) return this;
if (this.operator == (left ? "||" : "&&")) return left;
var right = this.right._eval(compressor, ignore_side_effects, cached, depth);
var rhs_ignore_side_effects = ignore_side_effects && !(left && typeof left == "object");
var right = this.right._eval(compressor, rhs_ignore_side_effects, cached, depth);
if (right === this.right) return this;
var result;
switch (this.operator) {
@@ -3568,6 +3587,7 @@ merge(Compressor.prototype, {
escaped[0].walk(new TreeWalker(function(node) {
if (found) return true;
if (node === ref) return found = true;
if (node instanceof AST_Scope) return true;
}));
return found;
default:
@@ -4292,6 +4312,7 @@ merge(Compressor.prototype, {
return sym;
};
var assign_in_use = Object.create(null);
var for_ins = Object.create(null);
var in_use = [];
var in_use_ids = Object.create(null); // avoid expensive linear scans of in_use
var value_read = Object.create(null);
@@ -4454,7 +4475,7 @@ merge(Compressor.prototype, {
if (drop_funcs && node !== self && node instanceof AST_Defun) {
var def = node.name.definition();
if (!(def.id in in_use_ids)) {
log(node.name, "Dropping unused function {name} [{file}:{line},{col}]", template(node.name));
log(node.name, "Dropping unused function {name}");
def.eliminated++;
return in_list ? List.skip : make_node(AST_EmptyStatement, node);
}
@@ -4469,7 +4490,7 @@ merge(Compressor.prototype, {
if (!(sym.definition().id in in_use_ids)) {
sym.__unused = true;
if (trim) {
log(sym, "Dropping unused function argument {name} [{file}:{line},{col}]", template(sym));
log(sym, "Dropping unused function argument {name}");
a.pop();
}
} else {
@@ -4572,7 +4593,7 @@ merge(Compressor.prototype, {
AST_Node.warn("Side effects in initialization of unused variable {name} [{file}:{line},{col}]", template(def.name));
side_effects.push(value);
} else {
log(def.name, "Dropping unused variable {name} [{file}:{line},{col}]", template(def.name));
log(def.name, "Dropping unused variable {name}");
}
sym.eliminated++;
}
@@ -4582,23 +4603,37 @@ merge(Compressor.prototype, {
return !def || fn.name && def === fn.name.definition();
}
});
if (head.length == 0 && tail.length == duplicated) {
[].unshift.apply(side_effects, tail.map(function(def) {
AST_Node.warn("Dropping duplicated definition of variable {name} [{file}:{line},{col}]", template(def.name));
var sym = def.name.definition();
var ref = make_node(AST_SymbolRef, def.name, def.name);
sym.references.push(ref);
var assign = make_node(AST_Assign, def, {
operator: "=",
left: ref,
right: def.value
});
var index = indexOf_assign(sym, def);
if (index >= 0) assign_in_use[sym.id][index] = assign;
sym.eliminated++;
return assign;
}));
} else if (head.length > 0 || tail.length > 0) {
switch (head.length) {
case 0:
if (tail.length == 0) break;
if (tail.length == duplicated) {
[].unshift.apply(side_effects, tail.map(function(def) {
AST_Node.warn("Dropping duplicated definition of variable {name} [{file}:{line},{col}]", template(def.name));
var sym = def.name.definition();
var ref = make_node(AST_SymbolRef, def.name, def.name);
sym.references.push(ref);
var assign = make_node(AST_Assign, def, {
operator: "=",
left: ref,
right: def.value
});
var index = indexOf_assign(sym, def);
if (index >= 0) assign_in_use[sym.id][index] = assign;
sym.eliminated++;
return assign;
}));
break;
}
case 1:
if (tail.length == 0) {
var id = head[0].name.definition().id;
if (id in for_ins) {
node.definitions = head;
for_ins[id].init = node;
break;
}
}
default:
node.definitions = head.concat(tail);
body.push(node);
}
@@ -4607,16 +4642,7 @@ merge(Compressor.prototype, {
body: make_sequence(node, side_effects)
}));
}
switch (body.length) {
case 0:
return in_list ? List.skip : make_node(AST_EmptyStatement, node);
case 1:
return body[0];
default:
return in_list ? List.splice(body) : make_node(AST_BlockStatement, node, {
body: body
});
}
return insert_statements(body, node, in_list);
}
if (node instanceof AST_LabeledStatement && node.body instanceof AST_For) {
// Certain combination of unused name + side effect leads to invalid AST:
@@ -4667,19 +4693,31 @@ merge(Compressor.prototype, {
return !block ? node : in_list ? List.splice(block.body) : block;
} else if (node instanceof AST_ForIn) {
if (!drop_vars || !compressor.option("loops")) return;
if (!(node.init instanceof AST_Definitions)) return;
var sym = node.init.definitions[0].name;
if (sym.definition().id in in_use_ids) return;
if (!is_empty(node.body)) return;
log(sym, "Dropping unused loop variable {name} [{file}:{line},{col}]", template(sym));
var sym = node.init;
if (sym instanceof AST_Definitions) {
sym = sym.definitions[0].name;
} else while (sym instanceof AST_PropAccess) {
sym = sym.expression.tail_node();
}
var def = sym.definition();
if (!def) return;
if (def.id in in_use_ids) return;
if (def.scope !== self && member(def, self.enclosed)) return;
log(sym, "Dropping unused loop variable {name}");
if (for_ins[def.id] === node) delete for_ins[def.id];
var body = [];
var value = node.object.drop_side_effect_free(compressor);
if (value) {
AST_Node.warn("Side effects in object of for-in loop [{file}:{line},{col}]", template(sym));
return make_node(AST_SimpleStatement, node, {
AST_Node.warn("Side effects in object of for-in loop [{file}:{line},{col}]", value.start);
body.push(make_node(AST_SimpleStatement, node, {
body: value
});
}));
}
return in_list ? List.skip : make_node(AST_EmptyStatement, node);
if (node.init instanceof AST_Definitions && def.orig[0] instanceof AST_SymbolCatch) {
body.push(node.init);
}
return insert_statements(body, node, in_list);
} else if (node instanceof AST_Sequence) {
if (node.expressions.length == 1) return node.expressions[0];
}
@@ -4699,8 +4737,8 @@ merge(Compressor.prototype, {
drop_unused_call_args(call, compressor, fns_with_marked_args);
});
function log(sym, text, props) {
AST_Node[sym.unreferenced() ? "warn" : "info"](text, props);
function log(sym, text) {
AST_Node[sym.definition().references.length > 0 ? "info" : "warn"](text + " [{file}:{line},{col}]", template(sym));
}
function template(sym) {
@@ -4712,6 +4750,19 @@ merge(Compressor.prototype, {
};
}
function insert_statements(body, orig, in_list) {
switch (body.length) {
case 0:
return in_list ? List.skip : make_node(AST_EmptyStatement, orig);
case 1:
return body[0];
default:
return in_list ? List.splice(body) : make_node(AST_BlockStatement, orig, {
body: body
});
}
}
function track_assigns(def, node) {
if (def.scope !== self) return false;
if (!def.fixed || !node.fixed) assign_in_use[def.id] = false;
@@ -4788,6 +4839,17 @@ merge(Compressor.prototype, {
if (track_assigns(node_def, sym) && is_lhs(sym, node) !== sym) add_assigns(node_def, sym);
return true;
}
if (node instanceof AST_ForIn) {
if (node.init instanceof AST_SymbolRef && scope === self) {
var id = node.init.definition().id;
if (!(id in for_ins)) for_ins[id] = node;
}
if (!drop_vars || !compressor.option("loops")) return;
if (!is_empty(node.body)) return;
if (node.init.has_side_effects(compressor)) return;
node.object.walk(tw);
return true;
}
if (node instanceof AST_SymbolRef) {
node_def = node.definition();
if (!(node_def.id in in_use_ids)) {
@@ -6395,7 +6457,7 @@ merge(Compressor.prototype, {
}
}));
var code = OutputStream();
AST_BlockStatement.prototype._codegen.call(fun, fun, code);
AST_BlockStatement.prototype._codegen.call(fun, code);
self.args = [
make_node(AST_String, self, {
value: fun.argnames.map(function(arg) {
@@ -6890,72 +6952,70 @@ merge(Compressor.prototype, {
var SIGN_OPS = makePredicate("+ -");
var MULTIPLICATIVE_OPS = makePredicate("* / %");
OPT(AST_UnaryPrefix, function(self, compressor) {
var e = self.expression;
var op = self.operator;
var exp = self.expression;
if (compressor.option("evaluate")
&& self.operator == "delete"
&& !(e instanceof AST_SymbolRef
|| e instanceof AST_PropAccess
|| is_identifier_atom(e))) {
if (e instanceof AST_Sequence) {
e = e.expressions.slice();
e.push(make_node(AST_True, self));
return make_sequence(self, e).optimize(compressor);
&& op == "delete"
&& !(exp instanceof AST_SymbolRef
|| exp instanceof AST_PropAccess
|| is_identifier_atom(exp))) {
if (exp instanceof AST_Sequence) {
exp = exp.expressions.slice();
exp.push(make_node(AST_True, self));
return make_sequence(self, exp).optimize(compressor);
}
return make_sequence(self, [ e, make_node(AST_True, self) ]).optimize(compressor);
return make_sequence(self, [ exp, make_node(AST_True, self) ]).optimize(compressor);
}
if (compressor.option("sequences")) {
if (compressor.option("sequences") && !(op == "typeof" && is_undeclared_ref(exp.tail_node()))) {
var seq = lift_sequence_in_expression(self, compressor);
if (seq !== self) return seq.optimize(compressor);
}
if (compressor.option("side_effects") && self.operator == "void") {
e = e.drop_side_effect_free(compressor);
if (e) {
self.expression = e;
return self;
} else {
return make_node(AST_Undefined, self).optimize(compressor);
}
if (compressor.option("side_effects") && op == "void") {
exp = exp.drop_side_effect_free(compressor);
if (!exp) return make_node(AST_Undefined, self).optimize(compressor);
self.expression = exp;
return self;
}
if (compressor.option("booleans")) {
if (self.operator == "!" && e.is_truthy()) {
return make_sequence(self, [ e, make_node(AST_False, self) ]).optimize(compressor);
} else if (compressor.in_boolean_context()) switch (self.operator) {
if (op == "!" && exp.is_truthy()) {
return make_sequence(self, [ exp, make_node(AST_False, self) ]).optimize(compressor);
} else if (compressor.in_boolean_context()) switch (op) {
case "!":
if (e instanceof AST_UnaryPrefix && e.operator == "!") {
if (exp instanceof AST_UnaryPrefix && exp.operator == "!") {
// !!foo => foo, if we're in boolean context
return e.expression;
return exp.expression;
}
if (e instanceof AST_Binary) {
self = best_of(compressor, self, e.negate(compressor, first_in_statement(compressor)));
if (exp instanceof AST_Binary) {
self = best_of(compressor, self, exp.negate(compressor, first_in_statement(compressor)));
}
break;
case "typeof":
// typeof always returns a non-empty string, thus it's
// always true in booleans
AST_Node.warn("Boolean expression always true [{file}:{line},{col}]", self.start);
return (e instanceof AST_SymbolRef ? make_node(AST_True, self) : make_sequence(self, [
e,
return (exp instanceof AST_SymbolRef ? make_node(AST_True, self) : make_sequence(self, [
exp,
make_node(AST_True, self)
])).optimize(compressor);
}
}
if (self.operator == "-" && e instanceof AST_Infinity) e = e.transform(compressor);
if (op == "-" && exp instanceof AST_Infinity) exp = exp.transform(compressor);
if (compressor.option("evaluate")
&& e instanceof AST_Binary
&& SIGN_OPS[self.operator]
&& MULTIPLICATIVE_OPS[e.operator]
&& (e.left.is_constant() || !e.right.has_side_effects(compressor))) {
&& exp instanceof AST_Binary
&& SIGN_OPS[op]
&& MULTIPLICATIVE_OPS[exp.operator]
&& (exp.left.is_constant() || !exp.right.has_side_effects(compressor))) {
return make_node(AST_Binary, self, {
operator: e.operator,
left: make_node(AST_UnaryPrefix, e.left, {
operator: self.operator,
expression: e.left
operator: exp.operator,
left: make_node(AST_UnaryPrefix, exp.left, {
operator: op,
expression: exp.left
}),
right: e.right
right: exp.right
});
}
// avoids infinite recursion of numerals
return self.operator == "-" && (e instanceof AST_Number || e instanceof AST_Infinity)
return op == "-" && (exp instanceof AST_Number || exp instanceof AST_Infinity)
? self : try_evaluate(compressor, self);
});
@@ -7184,11 +7244,11 @@ merge(Compressor.prototype, {
var parent = compressor.parent();
if (compressor.option("comparisons") && self.is_boolean(compressor)) {
if (!(parent instanceof AST_Binary) || parent instanceof AST_Assign) {
var negated = make_node(AST_UnaryPrefix, self, {
var negated = best_of(compressor, self, make_node(AST_UnaryPrefix, self, {
operator: "!",
expression: self.negate(compressor, first_in_statement(compressor))
});
self = best_of(compressor, self, negated);
}));
if (negated !== self) return negated;
}
switch (self.operator) {
case ">": reverse("<"); break;
@@ -7496,13 +7556,15 @@ merge(Compressor.prototype, {
&& self.left.is_number(compressor)) {
if (self.left.left instanceof AST_Constant) {
var lhs = make_binary(self.left, self.operator, self.left.left, self.right, self.left.left.start, self.right.end);
self = make_binary(self, self.left.operator, lhs, self.left.right);
self = make_binary(self, self.left.operator, try_evaluate(compressor, lhs), self.left.right);
} else if (self.left.right instanceof AST_Constant) {
var rhs = make_binary(self.left, align(self.left.operator, self.operator), self.left.right, self.right, self.left.right.start, self.right.end);
if (self.left.operator != "-"
|| !self.right.value
|| rhs.evaluate(compressor)
|| !self.left.left.is_negative_zero()) {
var op = align(self.left.operator, self.operator);
var rhs = try_evaluate(compressor, make_binary(self.left, op, self.left.right, self.right));
if (rhs.is_constant()
&& !(self.left.operator == "-"
&& self.right.value != 0
&& +rhs.value == 0
&& self.left.left.is_negative_zero())) {
self = make_binary(self, self.left.operator, self.left.left, rhs);
}
}
@@ -7658,7 +7720,7 @@ merge(Compressor.prototype, {
}
function is_indexFn(node) {
return node instanceof AST_Call
return node.TYPE == "Call"
&& node.expression instanceof AST_Dot
&& indexFns[node.expression.property];
}
@@ -8520,7 +8582,9 @@ merge(Compressor.prototype, {
}
}
if (is_lhs(compressor.self(), parent)) return self;
if (compressor.option("sequences") && compressor.parent().TYPE != "Call") {
if (compressor.option("sequences")
&& parent.TYPE != "Call"
&& !(parent instanceof AST_ForIn && parent.init === self)) {
var seq = lift_sequence_in_expression(self, compressor);
if (seq !== self) return seq.optimize(compressor);
}
@@ -8631,8 +8695,11 @@ merge(Compressor.prototype, {
col: self.start.col
});
}
if (is_lhs(compressor.self(), compressor.parent())) return self;
if (compressor.option("sequences") && compressor.parent().TYPE != "Call") {
var parent = compressor.parent();
if (is_lhs(compressor.self(), parent)) return self;
if (compressor.option("sequences")
&& parent.TYPE != "Call"
&& !(parent instanceof AST_ForIn && parent.init === self)) {
var seq = lift_sequence_in_expression(self, compressor);
if (seq !== self) return seq.optimize(compressor);
}

View File

@@ -100,7 +100,7 @@ function OutputStream(options) {
}
}
var indentation = 0;
var indentation = options.indent_start;
var current_col = 0;
var current_line = 1;
var current_pos = 0;
@@ -191,10 +191,6 @@ function OutputStream(options) {
return name;
}
function make_indent(back) {
return repeat_string(" ", options.indent_start + indentation - back * options.indent_level);
}
/* -----[ beautification/minification ]----- */
var has_parens = false;
@@ -345,9 +341,7 @@ function OutputStream(options) {
};
var indent = options.beautify ? function(half) {
if (options.beautify) {
print(make_indent(half ? 0.5 : 0));
}
print(repeat_string(" ", half ? indentation - (options.indent_level >> 1) : indentation));
} : noop;
var with_indent = options.beautify ? function(col, cont) {
@@ -575,9 +569,9 @@ function OutputStream(options) {
get : get,
toString : get,
indent : indent,
indentation : function() { return indentation },
current_width : function() { return current_col - indentation },
should_break : function() { return options.width && this.current_width() >= options.width },
should_break : readonly ? noop : function() {
return options.width && current_col - indentation >= options.width;
},
has_parens : function() { return has_parens },
newline : newline,
print : print,
@@ -630,13 +624,7 @@ function OutputStream(options) {
var use_asm = false;
AST_Node.DEFMETHOD("print", function(stream, force_parens) {
var self = this, generator = self._codegen;
function doit() {
stream.prepend_comments(self);
self.add_source_map(stream);
generator(self, stream);
stream.append_comments(self);
}
var self = this;
stream.push_node(self);
if (force_parens || self.needs_parens(stream)) {
stream.with_parens(doit);
@@ -644,9 +632,14 @@ function OutputStream(options) {
doit();
}
stream.pop_node();
});
AST_Node.DEFMETHOD("_print", AST_Node.prototype.print);
function doit() {
stream.prepend_comments(self);
self.add_source_map(stream);
self._codegen(stream);
stream.append_comments(self);
}
});
AST_Node.DEFMETHOD("print_to_string", function(options) {
var s = OutputStream(options);
this.print(s);
@@ -689,78 +682,66 @@ function OutputStream(options) {
PARENS(AST_Unary, function(output) {
var p = output.parent();
return p instanceof AST_PropAccess && p.expression === this
|| p instanceof AST_Call && p.expression === this;
return (p instanceof AST_Call || p instanceof AST_PropAccess) && p.expression === this;
});
PARENS(AST_Sequence, function(output) {
var p = output.parent();
// (foo, bar)() or foo(1, (2, 3), 4)
return p instanceof AST_Call
// !(foo, bar, baz)
|| p instanceof AST_Unary
// [ 1, (2, 3), 4 ] ==> [ 1, 3, 4 ]
return p instanceof AST_Array
// 1 + (2, 3) + 4 ==> 8
|| p instanceof AST_Binary
// var a = (1, 2), b = a + a; ==> b == 4
|| p instanceof AST_VarDef
// (1, {foo:2}).foo or (1, {foo:2})["foo"] ==> 2
|| p instanceof AST_PropAccess && p.expression === this
// [ 1, (2, 3), 4 ] ==> [ 1, 3, 4 ]
|| p instanceof AST_Array
// { foo: (1, 2) }.foo ==> 2
|| p instanceof AST_ObjectProperty
// new (foo, bar) or foo(1, (2, 3), 4)
|| p instanceof AST_Call
// (false, true) ? (a = 10, b = 20) : (c = 30)
// ==> 20 (side effect, set a := 10 and b := 20)
|| p instanceof AST_Conditional;
|| p instanceof AST_Conditional
// { foo: (1, 2) }.foo ==> 2
|| p instanceof AST_ObjectProperty
// (1, {foo:2}).foo or (1, {foo:2})["foo"] ==> 2
|| p instanceof AST_PropAccess && p.expression === this
// !(foo, bar, baz)
|| p instanceof AST_Unary
// var a = (1, 2), b = a + a; ==> b == 4
|| p instanceof AST_VarDef;
});
PARENS(AST_Binary, function(output) {
var p = output.parent();
// (foo && bar)()
if (p instanceof AST_Call && p.expression === this)
return true;
// typeof (foo && bar)
if (p instanceof AST_Unary)
return true;
// (foo && bar)["prop"], (foo && bar).prop
if (p instanceof AST_PropAccess && p.expression === this)
return true;
// this deals with precedence: 3 * (2 + 1)
if (p instanceof AST_Binary) {
var po = p.operator, pp = PRECEDENCE[po];
var so = this.operator, sp = PRECEDENCE[so];
if (pp > sp
|| (pp == sp
&& this === p.right)) {
return true;
}
return pp > sp || (pp == sp && this === p.right);
}
// (foo && bar)()
if (p instanceof AST_Call) return p.expression === this;
// (foo && bar)["prop"], (foo && bar).prop
if (p instanceof AST_PropAccess) return p.expression === this;
// typeof (foo && bar)
if (p instanceof AST_Unary) return true;
});
PARENS(AST_PropAccess, function(output) {
var node = this;
var p = output.parent();
if (p instanceof AST_New && p.expression === this) {
// i.e. new (foo.bar().baz)
if (p instanceof AST_New && p.expression === node) {
// i.e. new (foo().bar)
//
// if there's one call into this subtree, then we need
// parens around it too, otherwise the call will be
// interpreted as passing the arguments to the upper New
// expression.
var parens = false;
this.walk(new TreeWalker(function(node) {
if (parens || node instanceof AST_Scope) return true;
if (node instanceof AST_Call) {
parens = true;
return true;
}
}));
return parens;
do {
node = node.expression;
} while (node instanceof AST_PropAccess);
return node.TYPE == "Call";
}
});
PARENS(AST_Call, function(output) {
var p = output.parent();
if (p instanceof AST_New && p.expression === this) return true;
if (p instanceof AST_New) return p.expression === this;
// https://bugs.webkit.org/show_bug.cgi?id=123506
if (output.option('webkit')) {
var g = output.parent(1);
@@ -773,11 +754,12 @@ function OutputStream(options) {
});
PARENS(AST_New, function(output) {
if (need_constructor_parens(this, output)) return false;
var p = output.parent();
if (!need_constructor_parens(this, output)
&& (p instanceof AST_PropAccess // (new Date).getTime(), (new Date)["getTime"]()
|| p instanceof AST_Call && p.expression === this)) // (new foo)(bar)
return true;
// (new foo)(bar)
if (p instanceof AST_Call) return p.expression === this;
// (new Date).getTime(), (new Date)["getTime"]()
return p instanceof AST_PropAccess;
});
PARENS(AST_Number, function(output) {
@@ -786,36 +768,29 @@ function OutputStream(options) {
var value = this.value;
// https://github.com/mishoo/UglifyJS/issues/115
// https://github.com/mishoo/UglifyJS/pull/1009
if (value < 0 || /^0/.test(make_num(value))) {
return true;
}
return value < 0 || /^0/.test(make_num(value));
}
});
PARENS([ AST_Assign, AST_Conditional ], function(output) {
var p = output.parent();
// !(a = false) → true
if (p instanceof AST_Unary)
return true;
// 1 + (a = 2) + 3 → 6, side effect setting a = 2
if (p instanceof AST_Binary && !(p instanceof AST_Assign))
return true;
if (p instanceof AST_Binary) return !(p instanceof AST_Assign);
// (a = func)() —or— new (a = Object)()
if (p instanceof AST_Call && p.expression === this)
return true;
if (p instanceof AST_Call) return p.expression === this;
// (a = foo) ? bar : baz
if (p instanceof AST_Conditional && p.condition === this)
return true;
if (p instanceof AST_Conditional) return p.condition === this;
// (a = foo)["prop"] —or— (a = foo).prop
if (p instanceof AST_PropAccess && p.expression === this)
return true;
if (p instanceof AST_PropAccess) return p.expression === this;
// !(a = false) → true
if (p instanceof AST_Unary) return true;
});
/* -----[ PRINTERS ]----- */
DEFPRINT(AST_Directive, function(self, output) {
var quote = self.quote;
var value = self.value;
DEFPRINT(AST_Directive, function(output) {
var quote = this.quote;
var value = this.value;
switch (output.option("quote_style")) {
case 0:
case 2:
@@ -828,7 +803,7 @@ function OutputStream(options) {
output.print(quote + value + quote);
output.semicolon();
});
DEFPRINT(AST_Debugger, function(self, output) {
DEFPRINT(AST_Debugger, function(output) {
output.print("debugger");
output.semicolon();
});
@@ -864,21 +839,21 @@ function OutputStream(options) {
force_statement(this.body, output);
});
DEFPRINT(AST_Statement, function(self, output) {
self.body.print(output);
DEFPRINT(AST_Statement, function(output) {
this.body.print(output);
output.semicolon();
});
DEFPRINT(AST_Toplevel, function(self, output) {
display_body(self.body, true, output, true);
DEFPRINT(AST_Toplevel, function(output) {
display_body(this.body, true, output, true);
output.print("");
});
DEFPRINT(AST_LabeledStatement, function(self, output) {
self.label.print(output);
DEFPRINT(AST_LabeledStatement, function(output) {
this.label.print(output);
output.colon();
self.body.print(output);
this.body.print(output);
});
DEFPRINT(AST_SimpleStatement, function(self, output) {
self.body.print(output);
DEFPRINT(AST_SimpleStatement, function(output) {
this.body.print(output);
output.semicolon();
});
function print_braced_empty(self, output) {
@@ -895,13 +870,14 @@ function OutputStream(options) {
});
} else print_braced_empty(self, output);
}
DEFPRINT(AST_BlockStatement, function(self, output) {
print_braced(self, output);
DEFPRINT(AST_BlockStatement, function(output) {
print_braced(this, output);
});
DEFPRINT(AST_EmptyStatement, function(self, output) {
DEFPRINT(AST_EmptyStatement, function(output) {
output.semicolon();
});
DEFPRINT(AST_Do, function(self, output) {
DEFPRINT(AST_Do, function(output) {
var self = this;
output.print("do");
output.space();
make_block(self.body, output);
@@ -913,7 +889,8 @@ function OutputStream(options) {
});
output.semicolon();
});
DEFPRINT(AST_While, function(self, output) {
DEFPRINT(AST_While, function(output) {
var self = this;
output.print("while");
output.space();
output.with_parens(function() {
@@ -922,7 +899,8 @@ function OutputStream(options) {
output.space();
self._do_print_body(output);
});
DEFPRINT(AST_For, function(self, output) {
DEFPRINT(AST_For, function(output) {
var self = this;
output.print("for");
output.space();
output.with_parens(function() {
@@ -951,7 +929,8 @@ function OutputStream(options) {
output.space();
self._do_print_body(output);
});
DEFPRINT(AST_ForIn, function(self, output) {
DEFPRINT(AST_ForIn, function(output) {
var self = this;
output.print("for");
output.space();
output.with_parens(function() {
@@ -964,7 +943,8 @@ function OutputStream(options) {
output.space();
self._do_print_body(output);
});
DEFPRINT(AST_With, function(self, output) {
DEFPRINT(AST_With, function(output) {
var self = this;
output.print("with");
output.space();
output.with_parens(function() {
@@ -975,7 +955,7 @@ function OutputStream(options) {
});
/* -----[ functions ]----- */
AST_Lambda.DEFMETHOD("_do_print", function(output, nokeyword) {
DEFPRINT(AST_Lambda, function(output, nokeyword) {
var self = this;
if (!nokeyword) {
output.print("function");
@@ -993,32 +973,23 @@ function OutputStream(options) {
output.space();
print_braced(self, output, true);
});
DEFPRINT(AST_Lambda, function(self, output) {
self._do_print(output);
});
/* -----[ jumps ]----- */
function print_jump(output, kind, target) {
output.print(kind);
if (target) {
output.space();
target.print(output);
}
output.semicolon();
function print_jump(kind, prop) {
return function(output) {
output.print(kind);
var target = this[prop];
if (target) {
output.space();
target.print(output);
}
output.semicolon();
};
}
DEFPRINT(AST_Return, function(self, output) {
print_jump(output, "return", self.value);
});
DEFPRINT(AST_Throw, function(self, output) {
print_jump(output, "throw", self.value);
});
DEFPRINT(AST_Break, function(self, output) {
print_jump(output, "break", self.label);
});
DEFPRINT(AST_Continue, function(self, output) {
print_jump(output, "continue", self.label);
});
DEFPRINT(AST_Return, print_jump("return", "value"));
DEFPRINT(AST_Throw, print_jump("throw", "value"));
DEFPRINT(AST_Break, print_jump("break", "label"));
DEFPRINT(AST_Continue, print_jump("continue", "label"));
/* -----[ if ]----- */
function make_then(self, output) {
@@ -1047,7 +1018,8 @@ function OutputStream(options) {
}
force_statement(self.body, output);
}
DEFPRINT(AST_If, function(self, output) {
DEFPRINT(AST_If, function(output) {
var self = this;
output.print("if");
output.space();
output.with_parens(function() {
@@ -1069,7 +1041,8 @@ function OutputStream(options) {
});
/* -----[ switch ]----- */
DEFPRINT(AST_Switch, function(self, output) {
DEFPRINT(AST_Switch, function(output) {
var self = this;
output.print("switch");
output.space();
output.with_parens(function() {
@@ -1087,28 +1060,30 @@ function OutputStream(options) {
});
});
});
AST_SwitchBranch.DEFMETHOD("_do_print_body", function(output) {
function print_branch_body(self, output) {
output.newline();
this.body.forEach(function(stmt) {
self.body.forEach(function(stmt) {
output.indent();
stmt.print(output);
output.newline();
});
});
DEFPRINT(AST_Default, function(self, output) {
}
DEFPRINT(AST_Default, function(output) {
output.print("default:");
self._do_print_body(output);
print_branch_body(this, output);
});
DEFPRINT(AST_Case, function(self, output) {
DEFPRINT(AST_Case, function(output) {
var self = this;
output.print("case");
output.space();
self.expression.print(output);
output.print(":");
self._do_print_body(output);
print_branch_body(self, output);
});
/* -----[ exceptions ]----- */
DEFPRINT(AST_Try, function(self, output) {
DEFPRINT(AST_Try, function(output) {
var self = this;
output.print("try");
output.space();
print_braced(self, output);
@@ -1121,7 +1096,8 @@ function OutputStream(options) {
self.bfinally.print(output);
}
});
DEFPRINT(AST_Catch, function(self, output) {
DEFPRINT(AST_Catch, function(output) {
var self = this;
output.print("catch");
output.space();
output.with_parens(function() {
@@ -1130,13 +1106,14 @@ function OutputStream(options) {
output.space();
print_braced(self, output);
});
DEFPRINT(AST_Finally, function(self, output) {
DEFPRINT(AST_Finally, function(output) {
output.print("finally");
output.space();
print_braced(self, output);
print_braced(this, output);
});
DEFPRINT(AST_Var, function(self, output) {
DEFPRINT(AST_Var, function(output) {
var self = this;
output.print("var");
output.space();
self.definitions.forEach(function(def, i) {
@@ -1161,7 +1138,8 @@ function OutputStream(options) {
node.print(output, parens);
}
DEFPRINT(AST_VarDef, function(self, output) {
DEFPRINT(AST_VarDef, function(output) {
var self = this;
self.name.print(output);
if (self.value) {
output.space();
@@ -1185,18 +1163,19 @@ function OutputStream(options) {
});
});
}
DEFPRINT(AST_Call, function(self, output) {
self.expression.print(output);
print_call_args(self, output);
DEFPRINT(AST_Call, function(output) {
this.expression.print(output);
print_call_args(this, output);
});
DEFPRINT(AST_New, function(self, output) {
DEFPRINT(AST_New, function(output) {
var self = this;
output.print("new");
output.space();
self.expression.print(output);
if (need_constructor_parens(self, output)) print_call_args(self, output);
});
DEFPRINT(AST_Sequence, function(self, output) {
self.expressions.forEach(function(node, index) {
DEFPRINT(AST_Sequence, function(output) {
this.expressions.forEach(function(node, index) {
if (index > 0) {
output.comma();
if (output.should_break()) {
@@ -1207,7 +1186,8 @@ function OutputStream(options) {
node.print(output);
});
});
DEFPRINT(AST_Dot, function(self, output) {
DEFPRINT(AST_Dot, function(output) {
var self = this;
var expr = self.expression;
expr.print(output);
var prop = self.property;
@@ -1228,35 +1208,38 @@ function OutputStream(options) {
output.print_name(prop);
}
});
DEFPRINT(AST_Sub, function(self, output) {
self.expression.print(output);
DEFPRINT(AST_Sub, function(output) {
this.expression.print(output);
output.print("[");
self.property.print(output);
this.property.print(output);
output.print("]");
});
DEFPRINT(AST_UnaryPrefix, function(self, output) {
var op = self.operator;
DEFPRINT(AST_UnaryPrefix, function(output) {
var op = this.operator;
var exp = this.expression;
output.print(op);
if (/^[a-z]/i.test(op)
|| (/[+-]$/.test(op)
&& self.expression instanceof AST_UnaryPrefix
&& /^[+-]/.test(self.expression.operator))) {
&& exp instanceof AST_UnaryPrefix
&& /^[+-]/.test(exp.operator))) {
output.space();
}
self.expression.print(output);
exp.print(output);
});
DEFPRINT(AST_UnaryPostfix, function(self, output) {
self.expression.print(output);
output.print(self.operator);
DEFPRINT(AST_UnaryPostfix, function(output) {
this.expression.print(output);
output.print(this.operator);
});
DEFPRINT(AST_Binary, function(self, output) {
DEFPRINT(AST_Binary, function(output) {
var self = this;
self.left.print(output);
output.space();
output.print(self.operator);
output.space();
self.right.print(output);
});
DEFPRINT(AST_Conditional, function(self, output) {
DEFPRINT(AST_Conditional, function(output) {
var self = this;
self.condition.print(output);
output.space();
output.print("?");
@@ -1268,10 +1251,10 @@ function OutputStream(options) {
});
/* -----[ literals ]----- */
DEFPRINT(AST_Array, function(self, output) {
output.with_square(function() {
var a = self.elements, len = a.length;
if (len > 0) output.space();
DEFPRINT(AST_Array, function(output) {
var a = this.elements, len = a.length;
output.with_square(len > 0 ? function() {
output.space();
a.forEach(function(exp, i) {
if (i) output.comma();
exp.print(output);
@@ -1281,12 +1264,13 @@ function OutputStream(options) {
if (i === len - 1 && exp instanceof AST_Hole)
output.comma();
});
if (len > 0) output.space();
});
output.space();
} : noop);
});
DEFPRINT(AST_Object, function(self, output) {
if (self.properties.length > 0) output.with_block(function() {
self.properties.forEach(function(prop, i) {
DEFPRINT(AST_Object, function(output) {
var props = this.properties;
if (props.length > 0) output.with_block(function() {
props.forEach(function(prop, i) {
if (i) {
output.print(",");
output.newline();
@@ -1296,7 +1280,7 @@ function OutputStream(options) {
});
output.newline();
});
else print_braced_empty(self, output);
else print_braced_empty(this, output);
});
function print_property_name(key, quote, output) {
@@ -1315,47 +1299,48 @@ function OutputStream(options) {
}
}
DEFPRINT(AST_ObjectKeyVal, function(self, output) {
DEFPRINT(AST_ObjectKeyVal, function(output) {
var self = this;
print_property_name(self.key, self.quote, output);
output.colon();
self.value.print(output);
});
AST_ObjectProperty.DEFMETHOD("_print_getter_setter", function(type, output) {
output.print(type);
output.space();
print_property_name(this.key.name, this.quote, output);
this.value._do_print(output, true);
});
DEFPRINT(AST_ObjectSetter, function(self, output) {
self._print_getter_setter("set", output);
});
DEFPRINT(AST_ObjectGetter, function(self, output) {
self._print_getter_setter("get", output);
});
DEFPRINT(AST_Symbol, function(self, output) {
var def = self.definition();
output.print_name(def && def.mangled_name || self.name);
function print_accessor(type) {
return function(output) {
var self = this;
output.print(type);
output.space();
print_property_name(self.key.name, self.quote, output);
self.value._codegen(output, true);
};
}
DEFPRINT(AST_ObjectGetter, print_accessor("get"));
DEFPRINT(AST_ObjectSetter, print_accessor("set"));
DEFPRINT(AST_Symbol, function(output) {
var def = this.definition();
output.print_name(def && def.mangled_name || this.name);
});
DEFPRINT(AST_Hole, noop);
DEFPRINT(AST_This, function(self, output) {
DEFPRINT(AST_This, function(output) {
output.print("this");
});
DEFPRINT(AST_Constant, function(self, output) {
output.print(self.value);
DEFPRINT(AST_Constant, function(output) {
output.print(this.value);
});
DEFPRINT(AST_String, function(self, output) {
output.print_string(self.value, self.quote);
DEFPRINT(AST_String, function(output) {
output.print_string(this.value, this.quote);
});
DEFPRINT(AST_Number, function(self, output) {
if (use_asm && self.start && self.start.raw != null) {
output.print(self.start.raw);
DEFPRINT(AST_Number, function(output) {
var start = this.start;
if (use_asm && start && start.raw != null) {
output.print(start.raw);
} else {
output.print(make_num(self.value));
output.print(make_num(this.value));
}
});
DEFPRINT(AST_RegExp, function(self, output) {
var regexp = self.value;
DEFPRINT(AST_RegExp, function(output) {
var regexp = this.value;
var str = regexp.toString();
var end = str.lastIndexOf("/");
if (regexp.raw_source) {
@@ -1389,7 +1374,7 @@ function OutputStream(options) {
}
}));
var p = output.parent();
if (p instanceof AST_Binary && /^in/.test(p.operator) && p.left === self)
if (p instanceof AST_Binary && /^in/.test(p.operator) && p.left === this)
output.print(" ");
});

View File

@@ -110,7 +110,6 @@ function mangle_properties(ast, options) {
cache: null,
debug: false,
keep_quoted: false,
only_cache: false,
regex: null,
reserved: null,
}, true);
@@ -213,7 +212,6 @@ function mangle_properties(ast, options) {
function can_mangle(name) {
if (unmangleable[name]) return false;
if (options.only_cache) return cache.has(name);
if (/^-?[0-9]+(\.[0-9]+)?(e[+-][0-9]+)?$/.test(name)) return false;
return true;
}

View File

@@ -96,8 +96,9 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
// pass 1: setup scope chaining and handle definitions
var self = this;
var scope = self.parent_scope = null;
var defun = null;
var next_def_id = 0;
var scope = self.parent_scope = null;
var tw = new TreeWalker(function(node, descend) {
if (node instanceof AST_Catch) {
var save_scope = scope;
@@ -149,7 +150,9 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
scope.def_variable(node).defun = defun;
}
});
self.next_def_id = 0;
self.make_def = function(orig, init) {
return new SymbolDef(++next_def_id, this, orig, init);
};
self.walk(tw);
// pass 2: find back references and eval
@@ -240,12 +243,6 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
}
});
AST_Scope.DEFMETHOD("make_def", function(orig, init) {
var top = this;
while (top.parent_scope) top = top.parent_scope;
return new SymbolDef(++top.next_def_id, this, orig, init);
});
AST_Toplevel.DEFMETHOD("def_global", function(node) {
var globals = this.globals, name = node.name;
if (globals.has(name)) {
@@ -259,23 +256,28 @@ AST_Toplevel.DEFMETHOD("def_global", function(node) {
}
});
function init_scope_vars(scope, parent) {
scope.cname = -1; // the current index for mangling functions/variables
scope.enclosed = []; // variables from this or outer scope(s) that are referenced from this or inner scopes
scope.uses_eval = false; // will be set to true if this or nested scope uses the global `eval`
scope.uses_with = false; // will be set to true if this or some nested scope uses the `with` statement
scope.parent_scope = parent; // the parent scope (null if this is the top level)
scope.functions = new Dictionary(); // map name to AST_SymbolDefun (functions defined in this scope)
scope.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions)
if (parent) scope.make_def = parent.make_def; // top-level tracking of SymbolDef instances
}
AST_Scope.DEFMETHOD("init_scope_vars", function(parent_scope) {
this.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions)
this.functions = new Dictionary(); // map name to AST_SymbolDefun (functions defined in this scope)
this.uses_with = false; // will be set to true if this or some nested scope uses the `with` statement
this.uses_eval = false; // will be set to true if this or nested scope uses the global `eval`
this.parent_scope = parent_scope; // the parent scope
this.enclosed = []; // a list of variables from this or outer scope(s) that are referenced from this or inner scopes
this.cname = -1; // the current index for mangling functions/variables
init_scope_vars(this, parent_scope);
});
AST_Lambda.DEFMETHOD("init_scope_vars", function() {
AST_Scope.prototype.init_scope_vars.apply(this, arguments);
AST_Lambda.DEFMETHOD("init_scope_vars", function(parent_scope) {
init_scope_vars(this, parent_scope);
this.uses_arguments = false;
this.def_variable(new AST_SymbolFunarg({
name: "arguments",
start: this.start,
end: this.end
end: this.end,
}));
});
@@ -387,18 +389,10 @@ AST_Symbol.DEFMETHOD("unmangleable", function(options) {
// labels are always mangleable
AST_Label.DEFMETHOD("unmangleable", return_false);
AST_Symbol.DEFMETHOD("unreferenced", function() {
return !this.definition().references.length && !this.scope.pinned();
});
AST_Symbol.DEFMETHOD("definition", function() {
return this.thedef;
});
AST_Symbol.DEFMETHOD("global", function() {
return this.definition().global;
});
function _default_mangler_options(options) {
options = defaults(options, {
eval : false,
@@ -558,8 +552,8 @@ AST_Sequence.DEFMETHOD("tail_node", function() {
AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options) {
options = _default_mangler_options(options);
base54.reset();
var fn = AST_Symbol.prototype.add_source_map;
try {
var fn = AST_Symbol.prototype.add_source_map;
AST_Symbol.prototype.add_source_map = function() {
if (!this.unmangleable(options)) base54.consider(this.name, -1);
};

View File

@@ -112,51 +112,29 @@ function return_this() { return this; }
function return_null() { return null; }
var List = (function() {
function List(a, f, backwards) {
var ret = [], top = [], i;
function doit() {
function List(a, f) {
var ret = [];
for (var i = 0; i < a.length; i++) {
var val = f(a[i], i);
var is_last = val instanceof Last;
if (is_last) val = val.v;
if (val instanceof AtTop) {
val = val.v;
if (val instanceof Splice) {
top.push.apply(top, backwards ? val.v.slice().reverse() : val.v);
} else {
top.push(val);
}
} else if (val !== skip) {
if (val instanceof Splice) {
ret.push.apply(ret, backwards ? val.v.slice().reverse() : val.v);
} else {
ret.push(val);
}
}
return is_last;
}
if (Array.isArray(a)) {
if (backwards) {
for (i = a.length; --i >= 0;) if (doit()) break;
ret.reverse();
top.reverse();
if (val === skip) continue;
if (val instanceof Splice) {
ret.push.apply(ret, val.v);
} else {
for (i = 0; i < a.length; ++i) if (doit()) break;
ret.push(val);
}
} else {
for (i in a) if (HOP(a, i)) if (doit()) break;
}
return top.concat(ret);
return ret;
}
List.is_op = function(val) {
return val === skip || val instanceof AtTop || val instanceof Last || val instanceof Splice;
return val === skip || val instanceof Splice;
};
List.splice = function(val) {
return new Splice(val);
};
List.at_top = function(val) { return new AtTop(val); };
List.splice = function(val) { return new Splice(val); };
List.last = function(val) { return new Last(val); };
var skip = List.skip = {};
function AtTop(val) { this.v = val; }
function Splice(val) { this.v = val; }
function Last(val) { this.v = val; }
function Splice(val) {
this.v = val;
}
return List;
})();

View File

@@ -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.10.2",
"version": "3.10.4",
"engines": {
"node": ">=0.8.0"
},

View File

@@ -8538,3 +8538,25 @@ issue_4051: {
}
expect_stdout: "undefined"
}
issue_4070: {
options = {
collapse_vars: true,
pure_getters: "strict",
reduce_vars: true,
}
input: {
console.log(function f() {
function g() {}
g.p++;
return f.p = g.p;
}());
}
expect: {
console.log(function f() {
function g() {}
return f.p = ++g.p;
}());
}
expect_stdout: "NaN"
}

View File

@@ -2848,3 +2848,60 @@ issue_4025: {
"1 1 1",
]
}
forin_var_1: {
options = {
unused: true,
}
input: {
var k;
for (k in [ 1, 2 ])
console.log(k);
for (k in { PASS: 3 })
console.log(k);
console.log(k);
}
expect: {
for (var k in [ 1, 2 ])
console.log(k);
for (k in { PASS: 3 })
console.log(k);
console.log(k);
}
expect_stdout: [
"0",
"1",
"PASS",
"PASS",
]
}
forin_var_2: {
options = {
unused: true,
}
input: {
console.log(function() {
switch (0) {
case function() {
for (a in 0);
}:
var b = 0;
}
for (var c = 0; a;);
var a;
}());
}
expect: {
console.log(function() {
switch (0) {
case function() {
for (a in 0);
}:
}
for (; a;);
var a;
}());
}
expect_stdout: "undefined"
}

View File

@@ -2869,3 +2869,42 @@ issue_4035: {
"true",
]
}
issue_4067: {
options = {
evaluate: true,
reduce_vars: true,
unsafe: true,
}
input: {
(function(a) {
(function(b) {
b[0] += 0;
console.log(+a);
})(a);
})([]);
}
expect: {
(function(a) {
(function(b) {
b[0] += 0;
console.log(+a);
})(a);
})([]);
}
expect_stdout: "NaN"
}
issue_4077: {
options = {
evaluate: true,
unsafe: true,
}
input: {
console.log((a = []) - (a[0]++, 1) || "PASS");
}
expect: {
console.log((a = []) - (a[0]++, 1) || "PASS");
}
expect_stdout: "PASS"
}

View File

@@ -1483,8 +1483,7 @@ issue_2663_2: {
}
expect: {
(function() {
var i;
for (i in { a: 1, b: 2, c: 3 })
for (var i in { a: 1, b: 2, c: 3 })
j = i, console.log(j);
var j;
})();

View File

@@ -277,8 +277,8 @@ join_object_assignments_forin: {
}
expect: {
console.log(function() {
var o = { a: "PASS" };
for (var a in o)
var o = { a: "PASS" }, a;
for (a in o)
return o[a];
}());
}

View File

@@ -756,7 +756,37 @@ empty_for_in_side_effects: {
expect_warnings: [
"WARN: Dropping unused variable b [test/compress/loops.js:4,16]",
"INFO: Dropping unused loop variable a [test/compress/loops.js:1,17]",
"WARN: Side effects in object of for-in loop [test/compress/loops.js:1,17]",
"WARN: Side effects in object of for-in loop [test/compress/loops.js:2,17]",
]
}
empty_for_in_prop_init: {
options = {
loops: true,
pure_getters: "strict",
unused: true,
}
input: {
console.log(function f() {
var a = "bar";
for ((a, f)[a] in console.log("foo"));
return a;
}());
}
expect: {
console.log(function() {
var a = "bar";
console.log("foo");
return a;
}());
}
expect_stdout: [
"foo",
"bar",
]
expect_warnings: [
"INFO: Dropping unused loop variable f [test/compress/loops.js:3,21]",
"WARN: Side effects in object of for-in loop [test/compress/loops.js:3,30]",
]
}
@@ -933,3 +963,128 @@ issue_3634_2: {
}
expect_stdout: "1"
}
issue_4075: {
options = {
loops: true,
unused: true,
}
input: {
var a = "FAIL";
(function() {
for (a in { PASS: 0 });
})()
console.log(a);
}
expect: {
var a = "FAIL";
(function() {
for (a in { PASS: 0 });
})()
console.log(a);
}
expect_stdout: "PASS"
}
issue_4082: {
options = {
keep_fargs: "strict",
loops: true,
unused: true,
}
input: {
var a = "PASS";
(function(a) {
for (a in "foo")
var b;
})();
console.log(a);
}
expect: {
var a = "PASS";
(function(a) {
for (a in "foo");
})();
console.log(a);
}
expect_stdout: "PASS"
}
issue_4084: {
options = {
keep_fargs: "strict",
loops: true,
reduce_vars: true,
unused: true,
}
input: {
console.log(function() {
function f(a) {
var b = a++;
for (a in "foo");
}
f();
return typeof a;
}());
}
expect: {
console.log(function() {
(function() {
0;
})();
return typeof a;
}());
}
expect_stdout: "undefined"
}
issue_4091_1: {
options = {
loops: true,
toplevel: true,
unused: true,
}
input: {
try {
throw "FAIL";
} catch (e) {
for (var e in 42);
}
console.log(e && e);
}
expect: {
try {
throw "FAIL";
} catch (e) {
var e;
}
console.log(e && e);
}
expect_stdout: "undefined"
}
issue_4091_2: {
options = {
loops: true,
toplevel: true,
unused: true,
}
input: {
try {
throw "FAIL";
} catch (e) {
for (e in 42);
var e;
}
console.log(e && e);
}
expect: {
try {
throw "FAIL";
} catch (e) {
var e;
}
console.log(e && e);
}
expect_stdout: "undefined"
}

View File

@@ -637,6 +637,22 @@ evaluate_7_unsafe_math: {
]
}
evaluate_8_unsafe_math: {
options = {
evaluate: true,
unsafe_math: true,
}
input: {
var a = [ "42" ];
console.log(a * (1 / 7));
}
expect: {
var a = [ "42" ];
console.log(+a / 7);
}
expect_stdout: "6"
}
NaN_redefined: {
options = {
evaluate: true,

View File

@@ -877,7 +877,7 @@ for_init_var: {
expect_stdout: "PASS"
}
forin: {
forin_1: {
options = {
sequences: true,
}
@@ -895,6 +895,49 @@ forin: {
expect_stdout: "PASS"
}
forin_2: {
options = {
evaluate: true,
inline: true,
reduce_vars: true,
sequences: true,
toplevel: true,
unused: true,
}
input: {
var o = {
p: 1,
q: 2,
};
var k = "k";
for ((console.log("exp"), o)[function() {
console.log("prop");
return k;
}()] in function() {
console.log("obj");
return o;
}())
console.log(o.k, o[o.k]);
}
expect: {
var o = {
p: 1,
q: 2,
};
for ((console.log("exp"), o)[console.log("prop"), "k"] in console.log("obj"), o)
console.log(o.k, o[o.k]);
}
expect_stdout: [
"obj",
"exp",
"prop",
"p 1",
"exp",
"prop",
"q 2",
]
}
call: {
options = {
sequences: true,
@@ -1112,3 +1155,25 @@ issue_3703: {
}
expect_stdout: "PASS"
}
issue_4079: {
options = {
sequences: true,
side_effects: true,
}
input: {
try {
typeof (0, A);
} catch (e) {
console.log("PASS");
}
}
expect: {
try {
A;
} catch (e) {
console.log("PASS");
}
}
expect_stdout: "PASS"
}

View File

@@ -1,9 +1,3 @@
var o = this;
for (var k in o) L17060: {
a++;
UNUSED: {
console.log(0 - .1 - .1 - .1);
}
var a;
console.log(k);

View File

@@ -1,15 +1,12 @@
// (beautified)
var o = this;
for (var k in o) {}
var a;
console.log(k);
// output: a
console.log(0 - 1 - .1 - .1);
// output: -1.2000000000000002
//
// minify: k
// minify: -1.2
//
// options: {
// "compress": {
// "unsafe_math": true
// },
// "mangle": false
// }

View File

@@ -24,6 +24,9 @@ describe("test/reduce.js", function() {
});
it("Should eliminate unreferenced labels", function() {
var result = reduce_test(read("test/input/reduce/label.js"), {
compress: {
unsafe_math: true,
},
mangle: false,
}, {
verbose: false,

View File

@@ -112,19 +112,18 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
// no structural AST changes before this point.
if (node.start._permute >= REPLACEMENTS.length) return;
if (parent instanceof U.AST_Assign
&& parent.left === node
|| parent instanceof U.AST_Unary
&& parent.expression === node
&& ["++", "--", "delete"].indexOf(parent.operator) >= 0) {
// ignore lvalues
// ignore lvalues
if (parent instanceof U.AST_Assign && parent.left === node) return;
if (parent instanceof U.AST_Unary && parent.expression === node) switch (parent.operator) {
case "++":
case "--":
case "delete":
return;
}
if ((parent instanceof U.AST_For || parent instanceof U.AST_ForIn)
&& parent.init === node && node instanceof U.AST_Var) {
// preserve for (var ...)
return node;
}
// preserve for (var xxx; ...)
if (parent instanceof U.AST_For && parent.init === node && node instanceof U.AST_Var) return node;
// preserve for (xxx in ...)
if (parent instanceof U.AST_ForIn && parent.init === node) return node;
// node specific permutations with no parent logic
@@ -452,6 +451,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
node.start = JSON.parse(JSON.stringify(node.start));
node.start._permute = 0;
}));
var before_iterations = testcase;
for (var c = 0; c < max_iterations; ++c) {
if (verbose && pass == 1 && c % 25 == 0) {
log("// reduce test pass " + pass + ", iteration " + c + ": " + testcase.length + " bytes");
@@ -494,7 +494,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
}
}
}
if (c == 0) break;
if (before_iterations === testcase) break;
if (verbose) {
log("// reduce test pass " + pass + ": " + testcase.length + " bytes");
}
@@ -557,7 +557,7 @@ function try_beautify(testcase, minify_options, expected, result_cache, timeout)
code: testcase,
};
} else {
var actual = run_code(result.code, toplevel, result_cache, timeout);
var actual = run_code(result.code, toplevel, result_cache, timeout).result;
if (!sandbox.same_stdout(expected, actual)) return {
code: testcase,
};
@@ -650,7 +650,15 @@ function wrap_with_console_log(node) {
function run_code(code, toplevel, result_cache, timeout) {
var key = crypto.createHash("sha1").update(code).digest("base64");
return result_cache[key] || (result_cache[key] = sandbox.run_code(code, toplevel, timeout));
var value = result_cache[key];
if (!value) {
var start = Date.now();
result_cache[key] = value = {
result: sandbox.run_code(code, toplevel, timeout),
elapsed: Date.now() - start,
};
}
return value;
}
function compare_run_code(code, minify_options, result_cache, max_timeout) {
@@ -658,21 +666,19 @@ function compare_run_code(code, minify_options, result_cache, max_timeout) {
if (minified.error) return minified;
var toplevel = sandbox.has_toplevel(minify_options);
var elapsed = Date.now();
var unminified_result = run_code(code, toplevel, result_cache, max_timeout);
elapsed = Date.now() - elapsed;
var timeout = Math.min(100 * elapsed, max_timeout);
var minified_result = run_code(minified.code, toplevel, result_cache, timeout);
var unminified = run_code(code, toplevel, result_cache, max_timeout);
var timeout = Math.min(100 * unminified.elapsed, max_timeout);
var minified_result = run_code(minified.code, toplevel, result_cache, timeout).result;
if (sandbox.same_stdout(unminified_result, minified_result)) {
return is_timed_out(unminified_result) && is_timed_out(minified_result) && {
if (sandbox.same_stdout(unminified.result, minified_result)) {
return is_timed_out(unminified.result) && is_timed_out(minified_result) && {
timed_out: true,
};
}
return {
unminified_result: unminified_result,
unminified_result: unminified.result,
minified_result: minified_result,
elapsed: elapsed,
elapsed: unminified.elapsed,
};
}

View File

@@ -27,7 +27,7 @@ process.on("beforeExit", function() {
if (queued > 3) {
process.stdout.write("0");
} else if (now - earliest > 0 && total > 1) {
process.stdout.write(Math.min(20 * (now - earliest) / (total - 1), 6300000).toFixed(0));
process.stdout.write(Math.min(20 * (now - earliest) / (total - 1), 18000000).toFixed(0));
} else {
process.stdout.write("3600000");
}

View File

@@ -496,11 +496,16 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
var label = createLabel(canBreak, canContinue);
canBreak = label.break || enableLoopControl(canBreak, CAN_BREAK);
canContinue = label.continue || enableLoopControl(canContinue, CAN_CONTINUE);
var optElementVar = "";
if (rng(5) > 1) {
optElementVar = "c = 1 + c; var " + createVarName(MANDATORY) + " = expr" + loop + "[key" + loop + "]; ";
}
return "{var expr" + loop + " = " + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "; " + label.target + " for (var key" + loop + " in expr" + loop + ") {" + optElementVar + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + "}}";
var key = rng(10) ? "key" + loop : getVarName();
return [
"{var expr" + loop + " = " + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "; ",
label.target + " for (",
/^key/.test(key) ? "var " : "",
key + " in expr" + loop + ") {",
rng(5) > 1 ? "c = 1 + c; var " + createVarName(MANDATORY) + " = expr" + loop + "[" + key + "]; " : "",
createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth),
"}}",
].join("");
case STMT_SEMI:
return use_strict && rng(20) === 0 ? '"use strict";' : ";";
case STMT_EXPR: